1098 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			1098 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
import {
 | 
						|
  deleteNote,
 | 
						|
  exportTimekeeping,
 | 
						|
  getListMaster,
 | 
						|
  getTheTimesheet,
 | 
						|
  updateMultipleUserWorkingTime,
 | 
						|
  updateNote,
 | 
						|
  updateWorkingDays,
 | 
						|
} from '@/api/Admin'
 | 
						|
import { update, Xdelete } from '@/rtk/helpers/CRUD'
 | 
						|
import { exportFile, get } from '@/rtk/helpers/apiService'
 | 
						|
import {
 | 
						|
  Avatar,
 | 
						|
  Box,
 | 
						|
  Button,
 | 
						|
  Drawer,
 | 
						|
  Image,
 | 
						|
  Menu,
 | 
						|
  MultiSelect,
 | 
						|
  Select,
 | 
						|
  Table,
 | 
						|
  Text,
 | 
						|
  Textarea,
 | 
						|
  TextInput,
 | 
						|
  Tooltip,
 | 
						|
  Modal,
 | 
						|
} from '@mantine/core'
 | 
						|
import { useDisclosure } from '@mantine/hooks'
 | 
						|
import { notifications } from '@mantine/notifications'
 | 
						|
import {
 | 
						|
  IconCheck,
 | 
						|
  IconExclamationMark,
 | 
						|
  IconPointFilled,
 | 
						|
  IconTrash,
 | 
						|
  IconX,
 | 
						|
  IconFileExcel,
 | 
						|
} 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 [exportModalOpened, setExportModalOpened] = useState(false)
 | 
						|
  const [exportOption, setExportOption] = useState('default')
 | 
						|
 | 
						|
  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 ?? [],
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  const handleExport = async (option: string) => {
 | 
						|
    try {
 | 
						|
      const timestamp = moment().format('DDMMYYYY_HHmmss')
 | 
						|
      const fileName = `Timekeeping_${date.month}_${date.year}_${timestamp}.xlsx`
 | 
						|
      
 | 
						|
      await exportFile(
 | 
						|
        exportTimekeeping,
 | 
						|
        {
 | 
						|
          month: date.month,
 | 
						|
          year: date.year,
 | 
						|
          working_days: workingDays,
 | 
						|
          option: option
 | 
						|
        },
 | 
						|
        fileName
 | 
						|
      )
 | 
						|
      setExportModalOpened(false)
 | 
						|
    } catch (error) {
 | 
						|
      console.error('Export error:', error)
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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>
 | 
						|
            <Button 
 | 
						|
              onClick={() => setExportModalOpened(true)} 
 | 
						|
              size="xs" 
 | 
						|
              ml="md"
 | 
						|
              bg={'green'}
 | 
						|
              leftSection={<IconFileExcel size={16} />}
 | 
						|
            >
 | 
						|
              Export
 | 
						|
            </Button>
 | 
						|
          </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>
 | 
						|
      <Modal
 | 
						|
        opened={exportModalOpened}
 | 
						|
        onClose={() => setExportModalOpened(false)}
 | 
						|
        title={
 | 
						|
          <Text pl={'sm'} fw={700} fz={'lg'}>
 | 
						|
            Export
 | 
						|
          </Text>
 | 
						|
        }
 | 
						|
        size="sm"
 | 
						|
      >
 | 
						|
        <Select
 | 
						|
          label="Option"
 | 
						|
          placeholder="Choose an option"
 | 
						|
          value={exportOption}
 | 
						|
          onChange={(value) => setExportOption(value || 'default')}
 | 
						|
          data={[
 | 
						|
            { value: 'default', label: 'Default' }
 | 
						|
          ]}
 | 
						|
          mb="md"
 | 
						|
        />
 | 
						|
        <Box style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
 | 
						|
          <Button variant="outline" onClick={() => setExportModalOpened(false)}>
 | 
						|
            Close
 | 
						|
          </Button>
 | 
						|
          <Button onClick={() => handleExport(exportOption)}>
 | 
						|
            Export
 | 
						|
          </Button>
 | 
						|
        </Box>
 | 
						|
      </Modal>
 | 
						|
    </div>
 | 
						|
  )
 | 
						|
}
 | 
						|
 | 
						|
export default Timekeeping
 |