diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 915e8935..f85bce90 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -136,7 +136,9 @@ "CENTER": "Center", "AND_COUNT_MORE": "and {{count}} more", "RETURN_TO_LOGIN": "Return to Login", - "NO_CENTER_FOUND": "No Center found" + "NO_CENTER_FOUND": "No Center found", + "FILTER_BY":"Filter By", + "ALL":"All" }, "LOGIN_PAGE": { "USERNAME": "Username", @@ -600,7 +602,13 @@ "NOT_STARTED":"Not Started", "COMPLETED":"Completed", "INPROGRESS":"In-Progress", - "NO_DATA_FOUND":"No {{entity}} found" + "NO_DATA_FOUND":"No {{entity}} found", + "NO_RESULT_FOUND":"No Observations found for {{entity}}", + "NO_OBSERVATION_EXPIRED":"No observation expired for {{entity}}", + "DAYS_LEFT":"Days left", + "THIS_OBSERVATION_EXPIRED":"This observation is expired" + + } } diff --git a/src/components/FilterSelect.tsx b/src/components/FilterSelect.tsx new file mode 100644 index 00000000..f6509f07 --- /dev/null +++ b/src/components/FilterSelect.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { FormControl, InputLabel, Select, MenuItem, SelectChangeEvent } from '@mui/material'; + +interface FilterSelectProps { + menuItems: { value: string; label: string }[]; + selectedOption: string; + handleFilterChange: (event: SelectChangeEvent) => void; + label: string; + sx?: object; +} + +const FilterSelect: React.FC = ({ + menuItems, + selectedOption, + handleFilterChange, + label, + sx = {} +}) => { + return ( + + {label} + + + ); +}; + +export default FilterSelect; diff --git a/src/components/ObservationCard.tsx b/src/components/ObservationCard.tsx index 2f9acf00..df5d34a7 100644 --- a/src/components/ObservationCard.tsx +++ b/src/components/ObservationCard.tsx @@ -1,7 +1,9 @@ -import { Box, Typography, Card, CardContent } from '@mui/material'; +import { Box, Typography, Card, CardContent, Tooltip } from '@mui/material'; import React, { useEffect, useState } from 'react'; import AssignmentOutlinedIcon from '@mui/icons-material/AssignmentOutlined'; import { formatEndDate } from '@/utils/Helper'; +import { useTranslation } from 'react-i18next'; +import { LeftDays } from '@/utils/app.constant'; interface ObservationCardProp { name?: string; @@ -20,34 +22,44 @@ const ObservationCard: React.FC = ({ startDate, endDate }) => { - const [remainingDays, setRemainingDays] = useState(""); + const [remainingDays, setRemainingDays] = useState(); const [remainingTimes, setRemainingTimes] = useState(); + const { t } = useTranslation(); useEffect(() => { const today = new Date(); - console.log("endDate", endDate) - if(endDate) - { + + if (endDate) { const targetDate = new Date(endDate.toString()); - console.log("targetDate", targetDate) - const diffTime = Math.abs(targetDate?.getTime() - today.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - - setRemainingTimes(diffDays) - if(diffDays) - { - - const remainingTime=formatEndDate({diffDays}) - setRemainingDays(remainingTime) - + + // Calculate the difference in time + const diffTime = (targetDate.getTime() - today.getTime()); + const diffDays = Math.ceil(diffTime / LeftDays.ONE_DAY_IN_MILLISECONDS); + + // Update remaining times and days + setRemainingTimes(diffDays); + + if (diffDays > 0) { + const remainingTime = formatEndDate({ diffDays }); + console.log("remainingTime",typeof remainingTime) + setRemainingDays(remainingTime); + + } else { + // If diffDays is 0 or negative, set the status to 'expired' + setRemainingDays(0); + setRemainingTimes(0) } - } - - }, [endDate]); + }, [endDate]); + return ( + = ({ }, width: '300px', cursor: 'pointer', - background: 'linear-gradient(135deg, #fff9e6 0%, #faf2d6 100%)', + background: "#FEF8F2", borderRadius: '16px', - border: '1px solid #f0e68c', + border: '1px solid #D0C5B4', height: '200px', // Fixed height for all cards display: 'flex', flexDirection: 'column', }} - onClick={() => onCardClick?.(id || '')} + onClick={remainingDays===0?()=>{}:() => onCardClick?.(id || '')} > - = ({ {remainingDays} left - + + )} = ({ + ); }; diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx index af7f90bd..5a29cac0 100644 --- a/src/components/Searchbar.tsx +++ b/src/components/Searchbar.tsx @@ -10,6 +10,8 @@ import { import SearchIcon from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; import { debounce } from '@/utils/Helper'; +import { Telemetry } from '@/utils/app.constant'; +import { telemetryFactory } from '@/utils/telemetry'; export interface SearchBarProps { onSearch: (value: string) => void; @@ -50,6 +52,25 @@ const SearchBar: React.FC = ({ { handleSearch(searchTerm); + + + const windowUrl = window.location.pathname; + const cleanedUrl = windowUrl.replace(/^\//, ''); + const env = cleanedUrl.split("/")[0]; +console.log(env) + const telemetryInteract = { + context: { + env: env, + cdata: [], + }, + edata: { + id: 'search-value:'+searchTerm, + type: Telemetry.SEARCH, + subtype: '', + pageid: cleanedUrl, + }, + }; + telemetryFactory.interact(telemetryInteract); } }; diff --git a/src/components/observations/ObservationComponent.tsx b/src/components/observations/ObservationComponent.tsx index 2c0c2abd..6226af1b 100644 --- a/src/components/observations/ObservationComponent.tsx +++ b/src/components/observations/ObservationComponent.tsx @@ -9,6 +9,8 @@ import { mock } from 'node:test'; import { showToastMessage } from '../Toastify'; import { useTranslation } from 'react-i18next'; import { useRouter } from 'next/router'; +import { Telemetry } from '@/utils/app.constant'; +import { telemetryFactory } from '@/utils/telemetry'; @@ -153,22 +155,59 @@ const ObservationComponent: React.FC = ({ observationQues const response= await updateSubmission({submissionId, submissionData}) if((event as CustomEvent).detail.status==="draft") { + showToastMessage( t('OBSERVATION.FORM_SAVED_SUCCESSFULLY'), 'success'); + t('OBSERVATION.FORM_SAVED_SUCCESSFULLY') - showToastMessage( t('OBSERVATION.FORM_SUBMIT_SUCCESSFULLY'), 'success'); // window.history.back(); // router.push( // `${localStorage.getItem('observationPath')}` // ); + const windowUrl = window.location.pathname; + const cleanedUrl = windowUrl.replace(/^\//, ''); + const telemetryInteract = { + context: { + env: 'observation', + cdata: [], + }, + edata: { + id: 'save-observation-successfully', + type: Telemetry.CLICK, + subtype: '', + pageid: cleanedUrl, + }, + }; + telemetryFactory.interact(telemetryInteract); } else if((event as CustomEvent).detail.status==="submit") { - showToastMessage( t('OBSERVATION.FORM_SAVED_SUCCESSFULLY'), 'success'); + showToastMessage( t('OBSERVATION.FORM_SUBMIT_SUCCESSFULLY'), 'success'); + // window.history.back(); router.push( `${localStorage.getItem('observationPath')}` ); + + + const windowUrl = window.location.pathname; + const cleanedUrl = windowUrl.replace(/^\//, ''); + const telemetryInteract = { + context: { + env: 'observation', + cdata: [], + }, + edata: { + id: 'submit-observation-successfully', + type: Telemetry.CLICK, + subtype: '', + pageid: cleanedUrl, + }, + }; + telemetryFactory.interact(telemetryInteract); + + + } }; diff --git a/src/pages/observation/[observationId]/index.tsx b/src/pages/observation/[observationId]/index.tsx index 5c42b6e1..11289b66 100644 --- a/src/pages/observation/[observationId]/index.tsx +++ b/src/pages/observation/[observationId]/index.tsx @@ -16,7 +16,7 @@ import React, { useEffect, useState, useMemo } from 'react'; import { useRouter } from 'next/router'; import Header from '@/components/Header'; // import AddEntityModal from '@/components/observations/AddEntityModal'; -import { ObservationEntityType, Role , ObservationStatus} from '@/utils/app.constant'; +import { ObservationEntityType, Role , ObservationStatus, Telemetry} from '@/utils/app.constant'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { GetStaticPaths } from 'next'; import { toPascalCase } from '@/utils/Helper'; @@ -34,11 +34,13 @@ import { addEntities, checkEntityStatus, fetchEntities, + targetSolution, } from '@/services/ObservationServices'; import { useTranslation } from 'react-i18next'; import { CheckBoxOutlineBlankRounded } from '@mui/icons-material'; import Entity from '@/components/observations/Entity'; import SearchBar from '@/components/Searchbar'; +import { telemetryFactory } from '@/utils/telemetry'; interface EntityData { cohortId?: string; name?: string; @@ -75,9 +77,13 @@ const ObservationDetails = () => { const [limit, setLimit] = React.useState(pageLimit); const [searchInput, setSearchInput] = useState(''); - const [description, setDescription] = useState(''); const { t } = useTranslation(); + const [observationData, setObservationData] = useState([]); + const [observationDescription, setObservationDescription] = useState(); + const [observationEndDate, setObservationEndDate] = useState(); + + const theme = useTheme(); @@ -140,9 +146,26 @@ const ObservationDetails = () => { fetchCohorts(); }, [searchInput]); - + useEffect(() => { + const fetchObservationData = async () => { + try { + const response = await targetSolution(); + setObservationData(response?.result?.data || []); + + + } catch (error) { + console.error('Error fetching cohort list:', error); + } + }; + fetchObservationData(); + }, []); + useEffect(() => { + const result = observationData?.find((item:any) => item._id === Id); + setObservationDescription(result?.description) + setObservationEndDate(result?.endDate) + }, [Id, observationData]); useEffect(() => { const fetchEntityList = async () => { @@ -239,14 +262,14 @@ const ObservationDetails = () => { if(entityId) { const response = await checkEntityStatus({ observationId, entityId }); - console.log("response.result.length",response.result.length) - if(response.result.length!==0) + console.log("response.result.length",response?.result?.length) + if(response?.result?.length!==0) { - if(response?.result[0]?.evidencesStatus[0]?.status==="draft") + if(response?.result[response?.result?.length-1]?.evidencesStatus[0]?.status==="draft") setFirstEntityStatus("draft") - else if(response?.result[0]?.evidencesStatus[0]?.status==="completed") + else if(response?.result[response?.result?.length-1]?.evidencesStatus[0]?.status==="completed") setFirstEntityStatus("completed") - else if(response?.result[0]?.evidencesStatus[0]?.status==="notstarted") + else if(response?.result[response?.result?.length-1]?.evidencesStatus[0]?.status==="notstarted") setFirstEntityStatus("notstarted") } @@ -320,6 +343,7 @@ const ObservationDetails = () => { } finally { } }; + if(selectedCohort && selectedCohort!=='') handleCohortChange(); }, [page, selectedCohort, searchInput]); @@ -352,6 +376,21 @@ const ObservationDetails = () => { setPage(0); setSelectedCohort(event.target.value); localStorage.setItem("selectedCohort",event.target.value) + const windowUrl = window.location.pathname; + const cleanedUrl = windowUrl.replace(/^\//, ''); + const telemetryInteract = { + context: { + env: 'observation', + cdata: [], + }, + edata: { + id: 'filter-by-center:'+event.target.value, + type: Telemetry.CLICK, + subtype: '', + pageid: cleanedUrl, + }, + }; + telemetryFactory.interact(telemetryInteract); }; const onStartObservation = (cohortId: any) => { @@ -360,6 +399,8 @@ const ObservationDetails = () => { const basePath = router.asPath.split('?')[0]; const newFullPath = `${basePath}/questionary`; const { observationName } = router.query; + const { Id } = router.query; + const queryParams = { cohortId: cohortId, Id: Id , observationName: observationName }; router.push({ @@ -460,13 +501,7 @@ const ObservationDetails = () => { }; - useEffect(() => { - const data= typeof window !== 'undefined' - ? localStorage.getItem("observationDescription") || '' - : ''; - setDescription(data) - }, []); - + return ( <>
@@ -498,12 +533,16 @@ const ObservationDetails = () => { {/* Increased the left side size */} - - {t('OBSERVATION.OBSERVATION_DETAILS')} + + {t('OBSERVATION.OBSERVATION_DETAILS')} - - {description} + + + {observationDescription} + + {t('CENTER_SESSION.END_DATE')}: {observationEndDate || "N/A"} + { }} > {entity !== ObservationEntityType?.CENTER && ( - + {t('ATTENDANCE.CENTER_NAME')} diff --git a/src/pages/observation/index.tsx b/src/pages/observation/index.tsx index bee7b1fe..959748a5 100644 --- a/src/pages/observation/index.tsx +++ b/src/pages/observation/index.tsx @@ -6,11 +6,14 @@ import { targetSolution } from '@/services/ObservationServices'; import { Box, Tab, Tabs, Typography } from '@mui/material'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { useRouter } from 'next/router'; -import { FormContext, FormContextType, Role, Status } from '@/utils/app.constant'; +import { FormContext, FormContextType, Role, Status, Telemetry } from '@/utils/app.constant'; import { entityList } from '../../../app.config'; import { useTranslation } from 'react-i18next'; import SearchBar from '@/components/Searchbar'; import { toPascalCase } from '@/utils/Helper'; +import { telemetryFactory } from '@/utils/telemetry'; +import { FormControl, InputLabel, Select, MenuItem, SelectChangeEvent } from '@mui/material'; +import FilterSelect from '@/components/FilterSelect'; const ObservationForms: React.FC = () => { const [entityNames, setEntityNames] = useState(); @@ -18,7 +21,15 @@ const ObservationForms: React.FC = () => { const [filteredObservationData, setFilteredObservationData] = useState([]); const router = useRouter(); const { t } = useTranslation(); - + const [selectedOption, setSelectedOption] = useState('all'); + const [sortOrder, setSortOrder] = useState('lowToHigh'); + const currentDate = new Date(); + const menuItems = [ + { value: 'all', label: t('COMMON.ALL') }, + { value: 'center', label: t('CENTERS.CENTERS') }, + { value: 'facilitator', label: t('COMMON.FACILITATORS') }, + { value: 'learner', label: t('COMMON.LEARNERS') } + ]; useEffect(() => { const fetchEntityList = async () => { try { @@ -43,6 +54,11 @@ const ObservationForms: React.FC = () => { const response = await targetSolution(); setObservationData(response?.result?.data || []); setFilteredObservationData(response?.result?.data || []); + // const data=response?.result?.data; + // data[1].endDate = "2027-11-15T14:26:18.803Z"; + // setObservationData(data || []); + // setFilteredObservationData(data || []); + } catch (error) { console.error('Error fetching cohort list:', error); } @@ -56,7 +72,8 @@ const ObservationForms: React.FC = () => { entity: string, observationName: string, id: string, - description:string + description:string, + endDate:String ) => { const fullPath = router.asPath; const [basePath, queryString] = fullPath.split('?'); @@ -64,6 +81,8 @@ const ObservationForms: React.FC = () => { let newFullPath = `${basePath}${newRoute}`; const queryParams = { entity: entity, observationName: observationName, Id: id }; localStorage.setItem("observationDescription", description) + localStorage.setItem("endDateForSelectedObservation", endDate.toString()) + router.push({ pathname: newFullPath, query: queryParams, @@ -75,6 +94,22 @@ const ObservationForms: React.FC = () => { const handleChange = (event: React.SyntheticEvent, newValue: number) => { setValue(newValue); + + const windowUrl = window.location.pathname; + const cleanedUrl = windowUrl.replace(/^\//, ''); + const telemetryInteract = { + context: { + env: 'teaching-center', + cdata: [], + }, + edata: { + id: newValue==1?'change-tab-to-active-observations':'change-tab-to-expired-observations', + type: Telemetry.CLICK, + subtype: '', + pageid: cleanedUrl, + }, + }; + telemetryFactory.interact(telemetryInteract); // if(newValue===1) // { // setFilteredObservationData([]) @@ -83,13 +118,94 @@ const ObservationForms: React.FC = () => { }; const handleSearch = (searchTerm: string) => { + // setSearchInput(searchTerm); + // const filteredData = observationData.filter((item: any) => + // item.name.toLowerCase().includes(searchTerm.toLowerCase()) + // ); + // setFilteredObservationData(filteredData); + setSearchInput(searchTerm); + + // Filter observation data based on the search term const filteredData = observationData.filter((item: any) => item.name.toLowerCase().includes(searchTerm.toLowerCase()) ); setFilteredObservationData(filteredData); + const telemetryInteract = { + context: { + env: 'observation', + cdata: [], + }, + edata: { + id: 'search-observations-bysearchterm:'+searchTerm, + type: Telemetry.SEARCH, + subtype: '', + pageid: 'observation', + }, + }; + telemetryFactory.interact(telemetryInteract); + + + }; + const handleFilterChange = (event: SelectChangeEvent) => { + setSelectedOption(event.target.value as string); + if(event.target.value==="all") + { + const role = localStorage.getItem('role'); + if (role) { + if (role === Role.TEAM_LEADER) { + setEntityNames(entityList.TEAM_LEADER); + } else if (role === Role.TEACHER) { + setEntityNames(entityList.TEACHER); + } + } + } + else{ + setEntityNames([event.target.value as string]) + } + + const telemetryInteract = { + context: { + env: 'observation', + cdata: [], + }, + edata: { + id: 'apply-entity-filter:'+event.target.value, + type: Telemetry.CLICK, + subtype: '', + pageid: 'observation', + }, + }; + telemetryFactory.interact(telemetryInteract); }; + + const handleSortChange = (event: SelectChangeEvent) => { + const selectedValue = event.target.value as string; + setSortOrder(selectedValue); + + const sortedData = [...filteredObservationData].sort((a, b) => { + const dateA = new Date(a.endDate); + const dateB = new Date(b.endDate); + return selectedValue === 'lowToHigh' ? dateA.getTime() - dateB.getTime() : dateB.getTime() - dateA.getTime(); + }); + + setFilteredObservationData(sortedData); + + const telemetryInteract = { + context: { + env: 'observation', + cdata: [], + }, + edata: { + id: 'apply-datewise-filter:'+selectedValue, + type: Telemetry.CLICK, + subtype: '', + pageid: 'observation', + }, + }; + telemetryFactory.interact(telemetryInteract); + }; return (
@@ -99,12 +215,40 @@ const ObservationForms: React.FC = () => { - + + {value===0 &&( + {t('OBSERVATION.DAYS_LEFT')} + + )} + {typeof window !== 'undefined' && window.localStorage&& localStorage.getItem('role')=== Role.TEAM_LEADER &&( + )} + + {entityNames && entityNames.map((name, index) => ( { {filteredObservationData.filter((item: any) => item.entityType === name).length > 0 && value===0? ( filteredObservationData - .filter((item: any) => item.entityType === name) + .filter((item: any) => item.entityType === name && new Date(item.endDate) > currentDate) .map((item: any) => ( - onCardClick(item?.solutionId, item?.entityType, item?.name, item?._id, item?.description) + onCardClick(item?.solutionId, item?.entityType, item?.name, item?._id, item?.description, item?.endDate) } description={item?.description} endDate={item?.endDate} /> )) - ) : value===0 &&( - + ) :value === 1 ? ( + filteredObservationData.filter((item: any) => item.entityType === name && new Date(item.endDate) <= currentDate).length > 0 ? ( + filteredObservationData + .filter((item: any) => item.entityType === name && new Date(item.endDate) <= currentDate) + .map((item: any) => ( + + + // onCardClick(item?.solutionId, item?.entityType, item?.name, item?._id, item?.description) + // } + description={item?.description} + endDate={item?.endDate} + /> + + )) + ) : searchInput === "" ?( + + {t('OBSERVATION.NO_OBSERVATION_EXPIRED', { + entity: toPascalCase(name), + })} + + ):( + + {t('OBSERVATION.NO_RESULT_FOUND', { + entity: toPascalCase(name), + })} + + ) + ) : searchInput === "" && value === 0 ? ( - Observations are coming soon for {toPascalCase(name)} + {t('OBSERVATION.NO_RESULT_FOUND', { + entity: toPascalCase(name), + })} + ) : ( + + {t('OBSERVATION.NO_RESULT_FOUND', { + entity: toPascalCase(name), + })} + )} + ))} diff --git a/src/utils/Helper.ts b/src/utils/Helper.ts index 4163a1a3..8d22856e 100644 --- a/src/utils/Helper.ts +++ b/src/utils/Helper.ts @@ -627,14 +627,14 @@ export function formatEndDate({diffDays}: any) { const months = Math.floor(remainingDays / 30.44); const days = Math.round(remainingDays % 30.44); - remainingTime = `${years} year(s)${months > 0 ? `, ${months} months` : ''}${days > 0 ? `, , ${days} days` : ''}`; + remainingTime = `${years} year(s)${months > 0 ? `, ${months} month(s)` : ''}${days > 0 ? `, ${days} day(s)` : ''}`; } else if (diffDays > 31) { const months = Math.floor(diffDays / 30.44); const days = Math.round(diffDays % 30.44); - remainingTime = `${months} months ${days > 0 ? ` , ${days} days` : ''}`; + remainingTime = `${months} month(s) ${days > 0 ? ` , ${days} day(s)` : ''}`; } else { - remainingTime = `${diffDays} days`; + remainingTime = `${diffDays} day(s)`; } return remainingTime; } diff --git a/src/utils/app.constant.ts b/src/utils/app.constant.ts index 74e61654..1216e661 100644 --- a/src/utils/app.constant.ts +++ b/src/utils/app.constant.ts @@ -109,6 +109,9 @@ export enum Pagination { ITEMS_PER_PAGE = 10, MAX_ITEMS = 50, } +export enum LeftDays { + ONE_DAY_IN_MILLISECONDS=1000 * 60 * 60 * 24 +} export enum Telemetry { CLICK = 'CLICK',