diff --git a/public/Logo_without_tagline.png b/public/Logo_without_tagline.png new file mode 100644 index 00000000..14fc6454 Binary files /dev/null and b/public/Logo_without_tagline.png differ diff --git a/src/components/AttendanceStatusListView.tsx b/src/components/AttendanceStatusListView.tsx new file mode 100644 index 00000000..1403291c --- /dev/null +++ b/src/components/AttendanceStatusListView.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { useTheme } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; +import { ATTENDANCE_ENUM } from '../utils/Helper'; + +import { Box, Typography } from '@mui/material'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; //present +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import CancelIcon from '@mui/icons-material/Cancel'; //absent +import HighlightOffIcon from '@mui/icons-material/HighlightOff'; +import { AttendanceStatusListViewProps } from '../utils/Interfaces'; + +const AttendanceStatusListView: React.FC = ({ + userData, + isEdit = false, + isBulkAction = false, + handleBulkAction = () => {}, + bulkAttendanceStatus = '' +}) => { + const { t } = useTranslation(); + const theme = useTheme(); + + const boxStyling = { + display: 'flex', + height: '56px', + // width: '100%', + borderBottom: `0.5px solid ${theme.palette.warning[400]}`, + padding: '8px', + alignItems: 'center', + borderRadius: isBulkAction ? '8px' : 0, + backgroundColor: isBulkAction ? theme.palette.warning[800] : 'none' + }; + + const handleClickAction = (isBulkAction: boolean, selectedAction: string, id?: string) => { + if (isEdit) { + handleBulkAction(isBulkAction, selectedAction, id); + } + }; + return ( + + + {isBulkAction ? t('ATTENDANCE.MARK_ALL') : userData?.name} + + + handleClickAction( + isBulkAction, + ATTENDANCE_ENUM.PRESENT, + isBulkAction ? '' : userData?.userId + ) + } + > + {[userData?.attendance, bulkAttendanceStatus].includes(ATTENDANCE_ENUM.PRESENT) ? ( + + ) : ( + + )} + theme.palette.warning[400] }}> + {t('ATTENDANCE.PRESENT')} + + + + handleClickAction( + isBulkAction, + ATTENDANCE_ENUM.ABSENT, + isBulkAction ? '' : userData?.userId + ) + } + > + {[userData?.attendance, bulkAttendanceStatus].includes(ATTENDANCE_ENUM.ABSENT) ? ( + + ) : ( + + )} + theme.palette.warning[400] }}> + {t('ATTENDANCE.ABSENT')} + + + + ); +}; + +export default AttendanceStatusListView; diff --git a/src/components/CohortCard.tsx b/src/components/CohortCard.tsx new file mode 100644 index 00000000..8c65d941 --- /dev/null +++ b/src/components/CohortCard.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { CardContent, CardMedia, Typography, Box } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; +import ApartmentIcon from '@mui/icons-material/Apartment'; +import SmartDisplayIcon from '@mui/icons-material/SmartDisplay'; +import { CohortCardProps } from '../utils/Interfaces'; +import { useRouter } from 'next/navigation'; + +const CohortCard: React.FC = ({ + showBackground, + isRemote, + cohortName, + cohortId +}) => { + const { t } = useTranslation(); + const theme = useTheme(); + const router = useRouter(); + const boxStyling = { + display: 'flex', + height: 56, + border: `1px solid ${theme.palette.warning.A100}`, + borderRadius: '8px', + cursor: 'pointer', + backgroundColor: theme.palette.warning.A400 + }; + + const cardMedia = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '54px', + height: '56px', + borderRadius: '8px 0px 0px 8px', + backgroundColor: !showBackground ? theme.palette.warning.A400 : theme.palette.primary.light + }; + + const iconMedia = { + alignSelf: 'center', + marginLeft: 'auto', + height: '1rem', + width: '1rem', + marginRight: 2, + display: !showBackground ? 'none' : 'block' + }; + + return ( + { + router.push(`/class-details/${cohortId}`); // Check route + }} + sx={boxStyling} + > + + {isRemote ? : } + + + + {!showBackground + ? isRemote + ? t('DASHBOARD.NEW_REMOTE_CLASS') + : t('DASHBOARD.NEW_PHYSICAL_CLASS') + : `${cohortName}`} + + + + + ); +}; + +export default CohortCard; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 00000000..ed0b1427 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +import MenuItem from '@mui/material/MenuItem'; +import MenuIcon from '@mui/icons-material/Menu'; +import { Box, Stack } from '@mui/material'; +import Divider from '@mui/material/Divider'; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from '@mui/material/styles'; +import { useRouter, usePathname } from 'next/navigation'; +import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined'; +import LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined'; +import { styled, alpha } from '@mui/material/styles'; +import { MenuProps } from '@mui/material/Menu'; +import Menu from '@mui/material/Menu'; +import Image from 'next/image'; +import LogoWithoutTagline from '../../public/Logo_without_tagline.png' + +const Header: React.FC = () => { + const router = useRouter(); + const { t } = useTranslation(); + const pathname = usePathname() + const theme = useTheme(); + + const StyledMenu = styled((props: MenuProps) => ( + + ))(() => ({ + '& .MuiPaper-root': { + borderRadius: 6, + marginTop: theme.spacing(1), + minWidth: 180, + color: theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[300], + boxShadow: + 'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px', + '& .MuiMenu-list': { + padding: '4px 0' + }, + '& .MuiMenuItem-root': { + '& .MuiSvgIcon-root': { + fontSize: 18, + color: theme.palette.text.secondary, + marginRight: theme.spacing(1.5) + }, + '&:active': { + backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity) + } + } + } + })); + + + const handleProfileClick = () => { + if (pathname !== '/profile') { + router.push('/profile'); + } + }; + const handleLogoutClick = () => { + router.push('/logout'); + }; + + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + return ( + <> + + + + + + logo router.push('/')} + style={{ cursor: 'pointer' }} + /> + + + + +
+ + + + {t('PROFILE.MY_PROFILE')}{' '} + + + + {t('COMMON.LOGOUT')} + + +
+
+ + + ); +}; +export default Header; diff --git a/src/components/MarkAttendance.tsx b/src/components/MarkAttendance.tsx new file mode 100644 index 00000000..47dea458 --- /dev/null +++ b/src/components/MarkAttendance.tsx @@ -0,0 +1,294 @@ +import React, { useEffect } from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; +import { ATTENDANCE_ENUM, formatDate } from '../utils/Helper'; +//components +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +//icons +import { Icon } from '@mui/material'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; //present +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import CancelIcon from '@mui/icons-material/Cancel'; //absent +import CloseIcon from '@mui/icons-material/Close'; +import HighlightOffIcon from '@mui/icons-material/HighlightOff'; +// import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; //Half-Day +// import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline'; +import { MarkAttendanceProps } from '../utils/Interfaces'; +import Snackbar, { SnackbarOrigin } from '@mui/material/Snackbar'; + +interface State extends SnackbarOrigin { + openModal: boolean; +} + +type IconType = React.ReactElement; + +const BootstrapDialog = styled(Dialog)(({ theme }) => ({ + '& .MuiDialogContent-root': { + padding: theme.spacing(2) + }, + '& .MuiDialogActions-root': { + padding: theme.spacing(2) + } +})); +const MarkAttendance: React.FC = ({ + isOpen, + isSelfAttendance = true, + date, + name, + currentStatus, + handleClose, + handleSubmit, + message +}) => { + const { t } = useTranslation(); + + const [status, setStatus] = React.useState(currentStatus); + useEffect(() => { + setStatus(currentStatus); + }, [currentStatus]); + const theme = useTheme(); + const SNACKBAR_AUTO_HIDE_DURATION = 5000; + const [openMarkUpdateAttendance, setOpenMarkUpdateAttendance] = React.useState(false); + const handleMarkUpdateAttendanceModal = () => + setOpenMarkUpdateAttendance(!openMarkUpdateAttendance); + const [openMarkClearAttendance, setOpenMarkClearAttendance] = React.useState(false); + const handleMarkClearAttendanceModal = () => { + setOpenMarkClearAttendance(!openMarkClearAttendance); + }; + const [state, setState] = React.useState({ + openModal: false, + vertical: 'top', + horizontal: 'center' + }); + const { vertical, horizontal, openModal } = state; + const submitUpdateAttendance = () => { + handleClose(); + handleMarkUpdateAttendanceModal(); + }; + const submitClearAttendance = () => { + setStatus(ATTENDANCE_ENUM.NOT_MARKED); + handleClose(); + handleMarkClearAttendanceModal(); + }; + const submitAttendance = (newState: SnackbarOrigin) => () => { + handleSubmit(date, status); + // setOpenMarkUpdateAttendance(!openMarkUpdateAttendance); + setState({ ...newState, openModal: true }); + setTimeout(() => { + handleClose2(); + }, SNACKBAR_AUTO_HIDE_DURATION); + }; + + const submitConfirmAttendance = (newState: SnackbarOrigin) => () => { + // setOpenMarkClearAttendance(!openMarkClearAttendance); + handleMarkUpdateAttendanceModal(); + handleSubmit(date, status); + + setState({ ...newState, openModal: true }); + setTimeout(() => { + handleClose2(); + }, SNACKBAR_AUTO_HIDE_DURATION); + }; + + const handleClose2 = () => { + setState({ ...state, openModal: false }); + }; + const handleClear = () => { + if (status !== ATTENDANCE_ENUM.NOT_MARKED) { + setStatus(ATTENDANCE_ENUM.NOT_MARKED); + } + }; + const getButtonComponent = (value: string, icon1: IconType, icon2: IconType, text: string) => { + //setStatus(currentStatus) + // console.log(status === value) + + return ( + setStatus(value)}> + {status === value ? icon1 : icon2} + {text} + + ); + }; + return ( + + + + + {currentStatus === ATTENDANCE_ENUM.NOT_MARKED + ? t('COMMON.MARK_ATTENDANCE') + : t('COMMON.UPDATE_ATTENDANCE')} + + + {formatDate(date)} + + + {/* Mark Attendance */} + + + + + + {!isSelfAttendance && {name}} + {getButtonComponent( + ATTENDANCE_ENUM.PRESENT, + , + , + t('ATTENDANCE.PRESENT') + )} + {getButtonComponent( + isSelfAttendance ? ATTENDANCE_ENUM.ON_LEAVE : ATTENDANCE_ENUM.ABSENT, + , + , + isSelfAttendance ? t('ATTENDANCE.ON_LEAVE') : t('ATTENDANCE.ABSENT') + )} + {/* {isSelfAttendance && + getButtonComponent( + ATTENDANCE_ENUM.HALF_DAY, + , + , + t('ATTENDANCE.HALF_DAY') + )} */} + + + + + + + + + + + + {t('ATTENDANCE.UPDATE_ATTENDANCE_ALERT')} + + + {/* Mark Attendance */} + + + + + + + + + + + {t('ATTENDANCE.CLEAR_ATTENDANCE_ALERT')} + + + {/* Mark Attendance */} + + + + + + + + + + ); +}; + +export default MarkAttendance; diff --git a/src/components/OverviewCard.tsx b/src/components/OverviewCard.tsx new file mode 100644 index 00000000..754de5c0 --- /dev/null +++ b/src/components/OverviewCard.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useTheme } from '@mui/material/styles'; +// import { useTranslation } from 'react-i18next'; + +import { Box, Typography } from '@mui/material'; +interface OverviewCardProps { + label: string; + value: string; +} + +const OverviewCard: React.FC = ({ label, value }) => { + const theme = useTheme(); +// const { t } = useTranslation(); + + return ( + + + + {label} + + + {value} + + + + ); +}; + +export default OverviewCard; diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 00000000..36635d13 --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -0,0 +1,720 @@ +'use client' +import React, { useEffect } from 'react'; +import Header from '../components/Header'; +import { + Box, + Button, + FormControl, + MenuItem, + Select, + SelectChangeEvent, + Stack, + Typography, +} from '@mui/material'; +import Divider from '@mui/material/Divider'; +import { useTranslation } from 'react-i18next'; +import ArrowForwardSharpIcon from '@mui/icons-material/ArrowForwardSharp'; +// import CohortCard from '../components/CohortCard'; +import TodayIcon from '@mui/icons-material/Today'; +import { useRouter } from 'next/navigation'; +import Backdrop from '@mui/material/Backdrop'; +import Modal from '@mui/material/Modal'; +import Fade from '@mui/material/Fade'; +import CloseIcon from '@mui/icons-material/Close'; +import AttendanceStatusListView from '../components/AttendanceStatusListView'; +import { useTheme } from '@mui/material/styles'; +import MarkAttendance from '../components/MarkAttendance'; +import { markAttendance, bulkAttendance } from '../services/AttendanceService'; +import { + AttendanceParams, + TeacherAttendanceByDateParams, +} from '../utils/Interfaces'; +import { cohortList } from '../services/CohortServices'; +import { getMyCohortMemberList } from '../services/MyClassDetailsService'; +import { getTodayDate, formatDate, getMonthName } from '../utils/Helper'; +import Loader from '../components/Loader'; +import { getTeacherAttendanceByDate } from '../services/AttendanceService'; +import { ATTENDANCE_ENUM } from '../utils/Helper'; +import Snackbar, { SnackbarOrigin } from '@mui/material/Snackbar'; +import Link from 'next/link'; +import OverviewCard from '@/components/OverviewCard'; +// import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; + +interface State extends SnackbarOrigin { + openModal: boolean; +} + +interface DashboardProps { + // buttonText: string; +} + +// interface DataItem { +// name: string; +// } + +interface user { + key: string; +} + +interface cohort { + cohortId: string; + name: string; + value: string; +} +let userId = localStorage.getItem('userId'); +let contextId: string = ''; + +const Dashboard: React.FC = () => { + const [open, setOpen] = React.useState(false); + // const [selfAttendanceDetails, setSelfAttendanceDetails] = React.useState(null); + const [cohortsData, setCohortsData] = React.useState>([]); + const [classes, setClasses] = React.useState(''); + const [userType, setUserType] = React.useState('Students'); + const [cohortId, setCohortId] = React.useState(null); + const [openMarkAttendance, setOpenMarkAttendance] = React.useState(false); + const [openMarkUpdateAttendance, setOpenMarkUpdateAttendance] = + React.useState(false); + const [cohortMemberList, setCohortMemberList] = React.useState>( + [] + ); + const [numberOfCohortMembers, setNumberOfCohortMembers] = React.useState(0); + const [currentDate, setCurrentDate] = React.useState(getTodayDate); + const [bulkAttendanceStatus, setBulkAttendanceStatus] = React.useState(''); + const [loading, setLoading] = React.useState(false); + const [AttendanceMessage, setAttendanceMessage] = React.useState(''); + const [attendanceStatus, setAttendanceStatus] = React.useState(''); + const [isAllAttendanceMarked, setIsAllAttendanceMarked] = + React.useState(false); + const [showUpdateButton, setShowUpdateButton] = React.useState(false); + const [state, setState] = React.useState({ + openModal: false, + vertical: 'top', + horizontal: 'center', + }); + const { vertical, horizontal, openModal } = state; + + const { t } = useTranslation(); + const router = useRouter(); + const limit = 100; + const page = 0; + // const userAttendance = [{ userId: localStorage.getItem('userId'), attendance: 'present' }]; + const attendanceDate = currentDate; + let contextId = classes; + // const [TeachercontextId, setTeacherContextId] = React.useState(""); + const userTypeData = ['Students', 'Self']; + const report = false; + const offset = 0; + const theme = useTheme(); + ``; + const modalContainer = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 300, + bgcolor: theme.palette.warning['A400'], + border: '2px solid #000', + boxShadow: 24, + p: 4, + }; + + useEffect(() => { + const fetchCohortList = async () => { + const userId = localStorage.getItem('userId'); + setLoading(true); + try { + if (userId) { + let name = 'User'; + const resp = await cohortList(name, userId); + + const extractedNames = resp?.result?.cohortData; + + localStorage.setItem('parentCohortId', extractedNames?.[0].parentId); + // setTeacherContextId(extractedNames[0].parentId) + + const filteredData = extractedNames + ?.flatMap((item: any) => { + const addressData = item.customField?.find( + (field: any) => field.label === 'address' + ); + const classTypeData = item.customField?.find( + (field: any) => field.label === 'Class Type' + ); + return [ + addressData + ? { + cohortId: item.cohortId, + name: item.name, + value: addressData.value, + } + : null, + classTypeData + ? { + cohortId: item.cohortId, + name: item.name, + value: classTypeData.value, + } + : null, + ]; + }) + ?.filter(Boolean); + setCohortsData(filteredData); + setClasses(filteredData?.[0].cohortId); + setShowUpdateButton(true); + setLoading(false); + } + } catch (error) { + console.error('Error fetching cohort list:', error); + setLoading(false); + } + }; + if (classes != '') { + fetchCohortList(); + } + }, []); + + useEffect(() => { + const getCohortMemberList = async () => { + setLoading(true); + const parentCohortId = localStorage.getItem('parentCohortId'); + const formattedDate: string = currentDate; + try { + if (userId && parentCohortId) { + const response = await getMyCohortMemberList({ + contextId, + attendanceDate, + report, + limit, + offset, + }); + const resp = response?.data; + setCohortMemberList(resp); + setNumberOfCohortMembers(resp?.length); + setLoading(false); + const TeachercontextId = parentCohortId.replace(/\n/g, ''); + + const attendanceData: TeacherAttendanceByDateParams = { + fromDate: formattedDate, + toDate: formattedDate, + filters: { + userId, + contextId: TeachercontextId, + }, + }; + const response2 = await getTeacherAttendanceByDate(attendanceData); + if (response?.data?.length === 0) { + setAttendanceStatus(ATTENDANCE_ENUM.NOT_MARKED); + } else { + setAttendanceStatus(response2.data[0].attendance); + } + } + } catch (error) { + console.error('Error fetching cohort list:', error); + setLoading(false); + } + }; + + if (classes.length) { + getCohortMemberList(); + } + }, [classes]); + + const handleModalToggle = () => setOpen(!open); + const handleMarkAttendanceModal = () => + setOpenMarkAttendance(!openMarkAttendance); + const handleMarkUpdateAttendanceModal = () => + setOpenMarkUpdateAttendance(!openMarkUpdateAttendance); + + const handleChange = (event: SelectChangeEvent) => { + setClasses(event.target.value as string); + }; + + const handleUserChange = (event: SelectChangeEvent) => { + setUserType(event.target.value as string); + }; + + const submitAttendance = async (date: string, status: string) => { + const parentCohortId = localStorage.getItem('parentCohortId'); + + const formattedDate: string = currentDate; + //console.log(date, status); + if (userId && parentCohortId) { + const TeachercontextId = parentCohortId.replace(/\n/g, ''); + + const attendanceData: AttendanceParams = { + attendanceDate: date, + attendance: status, + userId, + contextId: TeachercontextId, + }; + setLoading(true); + try { + const response = await markAttendance(attendanceData); + if (response) { + setAttendanceMessage(t('ATTENDANCE.ATTENDANCE_MARKED_SUCCESSFULLY')); + + // const TeachercontextId = parentCohortId.replace(/\n/g, ''); + + const attendanceData: TeacherAttendanceByDateParams = { + fromDate: formattedDate, + toDate: formattedDate, + filters: { + userId, + contextId: TeachercontextId, + }, + }; + const response = await getTeacherAttendanceByDate(attendanceData); + if (response?.data?.length === 0) { + setAttendanceStatus(ATTENDANCE_ENUM.NOT_MARKED); + } else { + setAttendanceStatus(response.data[0].attendance); + } + } + setLoading(false); + } catch (error) { + setAttendanceMessage(t('ATTENDANCE.ATTENDANCE_MARKED_UNSUCCESSFULLY')); + console.error('error', error); + setLoading(false); + } + } + }; + + const submitBulkAttendanceAction = ( + isBulkAction: boolean, + status: string, + id?: string | undefined + ) => { + const updatedAttendanceList = cohortMemberList?.map((user: any) => { + if (isBulkAction) { + user.attendance = status; + setBulkAttendanceStatus(status); + } else { + setBulkAttendanceStatus(''); + if (user.userId === id) { + user.attendance = status; + } + } + return user; + }); + setCohortMemberList(updatedAttendanceList); + const hasEmptyAttendance = () => { + const allAttendance = updatedAttendanceList.some( + (user) => user.attendance === '' + ); + setIsAllAttendanceMarked(!allAttendance); + if (!allAttendance) { + setShowUpdateButton(true); + } + }; + hasEmptyAttendance(); + }; + const viewAttendanceHistory = () => { + router.push('/user-attendance-history'); //Check Route + }; + + const handleSave = () => { + const userAttendance = cohortMemberList?.map((user: any) => { + return { + userId: user.userId, + attendance: user.attendance, + }; + }); + if (userAttendance) { + const data = { + attendanceDate: currentDate, + contextId, + userAttendance, + }; + const markBulkAttendance = async () => { + setLoading(true); + try { + const response = await bulkAttendance(data); + // console.log(`response bulkAttendance`, response?.responses); + // const resp = response?.data; + // console.log(`data`, data); + setShowUpdateButton(true); + setLoading(false); + } catch (error) { + console.error('Error fetching cohort list:', error); + setLoading(false); + } + handleClick({ vertical: 'bottom', horizontal: 'center' })(); + }; + markBulkAttendance(); + } + }; + // console.log('att', attendanceStatus); + + useEffect(() => { + //let userId = '70861cf2-d00c-475a-a909-d58d0062c880'; + //"contextId": "17a82258-8b11-4c71-8b93-b0cac11826e3" + // contextId = '17a82258-8b11-4c71-8b93-b0cac11826e3'; + + //setContextId('17a82258-8b11-4c71-8b93-b0cac11826e3') // this one is for testing purpose + const fetchUserDetails = async () => { + try { + const parentCohortId = localStorage.getItem('parentCohortId'); + + const today: Date = new Date(); + const year: number = today.getFullYear(); + let month: number | string = today.getMonth() + 1; // Month is zero-based, so we add 1 + let day: number | string = today.getDate(); + + // Pad single-digit months and days with a leading zero + month = month < 10 ? '0' + month : month; + day = day < 10 ? '0' + day : day; + + const formattedDate: string = `${year}-${month}-${day}`; + + if (userId && parentCohortId) { + const TeachercontextId = parentCohortId.replace(/\n/g, ''); + + const attendanceData: TeacherAttendanceByDateParams = { + fromDate: formattedDate, + toDate: formattedDate, + filters: { + userId, + contextId: TeachercontextId, + }, + }; + const response = await getTeacherAttendanceByDate(attendanceData); + if (response?.data?.length === 0) { + setAttendanceStatus(ATTENDANCE_ENUM.NOT_MARKED); + } else { + setAttendanceStatus(response?.data[0]?.attendance); + } + } + } catch (Error) { + console.error(Error); + } + }; + fetchUserDetails(); + }, []); + + const handleClick = (newState: SnackbarOrigin) => () => { + setState({ ...newState, openModal: true }); + }; + const handleClose = () => { + setState({ ...state, openModal: false }); + }; + + return ( + +
+ + {t('DASHBOARD.DASHBOARD')} + + {loading && ( + + )} + + + + {t('DASHBOARD.DAY_WISE_ATTENDANCE')} + + {getMonthName()} + + + + + + + + + + {userType == "Students"? + + + : null } + + + + + { userType == "Students" ? + ( + {/* {t('DASHBOARD.NOT_MARKED')} */} + {/* {t('DASHBOARD.FUTURE_DATE_CANT_MARK')} + */} + + {t('DASHBOARD.PERCENT_ATTENDANCE')} + + + {t('DASHBOARD.PRESENT_STUDENTS')} + + ) + : ( + + {/* {t('DASHBOARD.NOT_MARKED')} */} + {/* {t('DASHBOARD.FUTURE_DATE_CANT_MARK')} + */} + {/* + {t('ATTENDANCE.PRESENT')} + */} + + {t('ATTENDANCE.ON_LEAVE')} + + + )} + + + + + + + + + + + + {t('COMMON.MARK_STUDENT_ATTENDANCE')} + + + {formatDate(currentDate)} + + + handleModalToggle()}> + + + + + {loading && ( + + )} + + + {t('ATTENDANCE.TOTAL_STUDENTS', { + count: numberOfCohortMembers, + })} + + {cohortMemberList && cohortMemberList?.length != 0 ? ( + + + + {cohortMemberList?.map((user: any) => ( + + ))} + + + + + + + ) : ( + + {t('COMMON.NO_DATA_FOUND')} + + )} + + + + + + + + + + {t('DASHBOARD.OVERVIEW')} + + + + {t('DASHBOARD.MORE_DETAILS')} + + + + {loading && ( + + )} + + + + {userType == "Students" ? + ( + + + ): + ( )} + + + ); +}; + +export default Dashboard; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 4df6af30..78450b2f 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,4 +1,3 @@ -'use client'; import React, { useEffect, useMemo } from 'react'; import { Box, @@ -60,9 +59,9 @@ const LoginPage = () => { const { vertical, horizontal, openModal } = state; useEffect(() => { - const refresh_token = localStorage.getItem('refreshToken'); - if (refresh_token) { - router.push('/dashboard'); + const refreshToken = localStorage.getItem('refreshToken'); + if (refreshToken) { + router.push('/Dashboard'); } }, []); @@ -118,7 +117,7 @@ const LoginPage = () => { // localStorage.setItem('userId', userResponse?.userId); } setLoading(false); - router.push('/dashboard'); + router.push('/Dashboard'); } catch (error: any) { setLoading(false); if (error.response && error.response.status === 404) { diff --git a/src/services/AttendanceService.ts b/src/services/AttendanceService.ts new file mode 100644 index 00000000..6de52427 --- /dev/null +++ b/src/services/AttendanceService.ts @@ -0,0 +1,60 @@ +import { post } from './RestClient'; +import { + AttendanceParams, + BulkAttendanceParams, + AttendanceByDateParams, + TeacherAttendanceByDateParams, + AttendanceReports +} from '../utils/Interfaces'; + +export const markAttendance = async ({ + userId, + attendanceDate, + attendance, + contextId +}: AttendanceParams): Promise => { + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/attendance`; + try { + const response = await post(apiUrl, { userId, attendanceDate, attendance, contextId }); + return response?.data; + } catch (error) { + console.error('error in marking attendance', error); + throw error; + } +}; + +export const bulkAttendance = async ({ + attendanceDate, + contextId, + userAttendance +}: BulkAttendanceParams): Promise => { + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/attendance/bulkAttendance`; + try { + const response = await post(apiUrl, { attendanceDate, contextId, userAttendance }); + return response?.data; + } catch (error) { + console.error('error in marking bulk attendance', error); + } +}; + +export const getTeacherAttendanceByDate = async ({ + fromDate, + toDate, + filters: { userId, contextId } +}: TeacherAttendanceByDateParams): Promise => { + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/attendance/bydate`; + try { + const response = await post(apiUrl, { + fromDate, + toDate, + filters: { + contextId, + userId + } + }); + return response?.data; + } catch (error) { + console.error('error in marking attendance', error); + throw error; + } +}; diff --git a/src/services/CohortServices.ts b/src/services/CohortServices.ts new file mode 100644 index 00000000..5d598b41 --- /dev/null +++ b/src/services/CohortServices.ts @@ -0,0 +1,14 @@ +import { get } from './RestClient'; + +export const cohortList = async (name: string, userId: string): Promise => { + const apiUrl: string = `${ + process.env.NEXT_PUBLIC_BASE_URL + }/cohort/cohortDetails?name=${name}&id=${userId}`; + try { + const response = await get(apiUrl); + return response?.data; + } catch (error) { + console.error('error in getting cohort list', error); + throw error; + } +}; diff --git a/src/services/MyClassDetailsService.ts b/src/services/MyClassDetailsService.ts new file mode 100644 index 00000000..8d7be27f --- /dev/null +++ b/src/services/MyClassDetailsService.ts @@ -0,0 +1,28 @@ +import { cohortDetailsList } from '../utils/Interfaces'; +import { post } from './RestClient'; + +export const getMyCohortMemberList = async ({ + contextId, + attendanceDate, + report, + limit, + offset, + filters +}: cohortDetailsList): Promise => { + const apiUrl: string = `${process.env.NEXT_PUBLIC_BASE_URL}/attendance/report`; + try { + const response = await post(apiUrl, { + contextId, + attendanceDate, + report, + limit, + offset, + filters + }); //contextId, report, limit, offset, filters + console.log('data', response?.data); + return response?.data; + } catch (error) { + console.error('error in attendance report api ', error); + throw error; + } +}; diff --git a/src/styles/customStyles.tsx b/src/styles/customStyles.tsx index ddf8f6e4..43dedc58 100644 --- a/src/styles/customStyles.tsx +++ b/src/styles/customStyles.tsx @@ -45,7 +45,8 @@ const customTheme = createTheme({ A100: '#D0C5B4', A200: '#4D4639', A400: '#FFFFFF', - A700: '#EDEDED' }, + A700: '#EDEDED', + A900: '#f8f0db' }, error: { main: '#BA1A1A', light: '#FFDAD6', diff --git a/src/styles/globals.css b/src/styles/globals.css index 74eda746..8215d05e 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -15,3 +15,6 @@ body { padding: 0; box-sizing: border-box; } +.word-break{ + word-break: break-all; +} \ No newline at end of file diff --git a/src/utils/Helper.ts b/src/utils/Helper.ts new file mode 100644 index 00000000..4f2e771e --- /dev/null +++ b/src/utils/Helper.ts @@ -0,0 +1,46 @@ +export const ATTENDANCE_ENUM = { + PRESENT: 'present', + ABSENT: 'absent', + HALF_DAY: 'half-day', + NOT_MARKED: 'notmarked', + ON_LEAVE: 'on-leave' + }; + + export const MONTHS = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + export const formatDate = (dateString: string) => { + const [year, monthIndex, day] = dateString.split('-'); + const month = MONTHS[parseInt(monthIndex, 10) - 1]; + return `${day} ${month}, ${year}`; + }; + + export const getTodayDate = () => { + const currentDate = new Date(); + const year = currentDate.getFullYear(); + const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Adding 1 as month is zero-indexed + const day = String(currentDate.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + + export const getMonthName = () => { + const currentDate = new Date(); + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + const monthIndex = currentDate.getMonth(); + return monthNames[monthIndex]; +}; \ No newline at end of file diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts new file mode 100644 index 00000000..742b8393 --- /dev/null +++ b/src/utils/Interfaces.ts @@ -0,0 +1,102 @@ +export interface AttendanceParams { + userId: string; + attendanceDate: string; + attendance: string; + contextId: string; + } + + export interface CohortCardProps { + showBackground: boolean; + isRemote: boolean; + cohortName: string; + cohortId: string; + } + + export interface MarkAttendanceProps { + isOpen: boolean; + isSelfAttendance?: boolean; + date: string; + name?: string; + currentStatus: string; + handleClose: () => void; + + handleSubmit: (attendanceDate: string, attendance: string) => void; + message?: string; + } + + export interface AttendanceStatusListViewProps { + userData?: UserAttendanceObj; + isEdit?: boolean; + isBulkAction?: boolean; + handleBulkAction?: (isBulkAction: boolean, status: string, id?: string | undefined) => void; + bulkAttendanceStatus?: string; + } + + export interface UserAttendanceObj { + userId: string; + attendance: string; + name?: string; + } + + export interface BulkAttendanceParams { + attendanceDate: string; + contextId: string; + userAttendance: UserAttendanceObj[]; + } + + export interface cohortListParam { + name?: string; + userId?: string; + } + + export interface cohortDetailsList { + contextId?: string; + report: boolean; + limit: number; + offset: number; + filters?: object; + attendanceDate?: string; + } + export interface AttendanceByDateParams { + fromDate: string; + toDate: string; + page: number; + filters: { + userId?: string; + contextId?: string; + }; + } + + export interface TeacherAttendanceByDateParams { + fromDate: string; + toDate: string; + filters: { + userId: string; + contextId: string; + }; + } + + interface CustomField { + label: string; + value: string; + } + export interface UserData { + id: number; + name: string; + role: string; + district: string; + state: string; + email: string; + dob?: string; + mobile?: string; + customFields: CustomField[]; + } + + export interface AttendanceReports { + contextId: string; + userId: string; + report: boolean; + limit: number; + filters: object; + } + \ No newline at end of file