Commit 4a3dab02 authored by =sankoju Ravi's avatar =sankoju Ravi

Merge branch 'master' of...

Merge branch 'master' of https://gitlab.mynisum.com/vsingamchetty/nisum-scorecard into feture/get-activities
parents a38e9755 60298025
...@@ -52,6 +52,7 @@ Data In body Example : ...@@ -52,6 +52,7 @@ Data In body Example :
"searchText":"eng" "searchText":"eng"
}*/ }*/
3. POST method to add activity added by manager to his reportees. Id should contain all the fields given below in the example or else it throws an error. 3. POST method to add activity added by manager to his reportees. Id should contain all the fields given below in the example or else it throws an error.
Note: Users can add a custom aName field , from frontend a unique id will be generated and added as aID. Note: Users can add a custom aName field , from frontend a unique id will be generated and added as aID.
...@@ -59,18 +60,20 @@ https://nisumscorecardservertesting.netlify.app/.netlify/functions/api/createAct ...@@ -59,18 +60,20 @@ https://nisumscorecardservertesting.netlify.app/.netlify/functions/api/createAct
Data In body Example : Data In body Example :
//Example of post Data //Example of post Data
/*
{ {
"empId":41689, "empId":10000,
"data":{ "data":{
"aName":"Approval of timesheet", "aName":"Approval of timesheet",
"aId":"D001", "aId":"D001",
"type":"duties", "type":"duties",
"ratedBy":"Name",
"score":3, "score":3,
"comments":"" "comments":""
} }
} }
Actual Activities Data this to be used when trying to create a activity to a employee: Actual Activities Data this to be used when trying to create a activity to a employee:
[{"_id":"65f19252ecd2b756fab896b8","type":"duties","aId":"D001","aName":"Submission timesheet"}, [{"_id":"65f19252ecd2b756fab896b8","type":"duties","aId":"D001","aName":"Submission timesheet"},
...@@ -91,8 +94,29 @@ Data In body Example : ...@@ -91,8 +94,29 @@ Data In body Example :
{ {
"empId":41689, "empId":41689,
"fromDate":"2024-03-10", "fromDate":"2024-03-10",
"toDate":"2024-03-14" "toDate":"2024-03-14",
page:0
perPage:10,
getAll:true //send only for get all records
}
*/
5. POST method to get Average scores of individual activities.
https://nisumscorecardservertesting.netlify.app/.netlify/functions/api/getActivities-avg
//sending filtered activities avg score data
/*Example post data
{
"empId":41689,
"fromDate":"2024-03-10",
"toDate":"2024-03-14",
"types":["duties","initiative"]
} }
*/
API's for Deployment Team Usage API's for Deployment Team Usage
...@@ -134,6 +158,7 @@ Data In body Example : ...@@ -134,6 +158,7 @@ Data In body Example :
"searchText":"eng" "searchText":"eng"
}*/ }*/
3. POST method to add activity added by manager to his reportees. Id should contain all the fields given below in the example or else it throws an error. 3. POST method to add activity added by manager to his reportees. Id should contain all the fields given below in the example or else it throws an error.
Note: Users can add a custom aName field , from frontend a unique id will be generated and added as aID. Note: Users can add a custom aName field , from frontend a unique id will be generated and added as aID.
...@@ -141,18 +166,19 @@ https://nisumscorecardserverdev.netlify.app/.netlify/functions/api/createActivit ...@@ -141,18 +166,19 @@ https://nisumscorecardserverdev.netlify.app/.netlify/functions/api/createActivit
Data In body Example : Data In body Example :
//Example of post Data //Example of post Data
/*
{ {
"empId":41689, "empId":10000,
"data":{ "data":{
"aName":"Approval of timesheet", "aName":"Approval of timesheet",
"aId":"D001", "aId":"D001",
"type":"duties", "type":"duties",
"ratedBy":"Name",
"score":3, "score":3,
"comments":"" "comments":""
} }
} }
Actual Activities Data this to be used when trying to create a activity to a employee: Actual Activities Data this to be used when trying to create a activity to a employee:
[{"_id":"65f19252ecd2b756fab896b8","type":"duties","aId":"D001","aName":"Submission timesheet"}, [{"_id":"65f19252ecd2b756fab896b8","type":"duties","aId":"D001","aName":"Submission timesheet"},
...@@ -168,12 +194,30 @@ Actual Activities Data this to be used when trying to create a activity to a emp ...@@ -168,12 +194,30 @@ Actual Activities Data this to be used when trying to create a activity to a emp
https://nisumscorecardserverdev.netlify.app/.netlify/functions/api/getActivities https://nisumscorecardserverdev.netlify.app/.netlify/functions/api/getActivities
Data In body Example : Data In body Example :
/*Example post data
{ {
"empId":41689, "empId":41689,
"fromDate":"2024-03-10", "fromDate":"2024-03-10",
"toDate":"2024-03-14" "toDate":"2024-03-14",
page:0
perPage:10,
getAll:true //send only for get all records
}
*/
5. POST method to get Average scores of individual activities.
https://nisumscorecardserverdev.netlify.app/.netlify/functions/api/getActivities-avg
//sending filtered activities avg score data
/*Example post data
{
"empId":41689,
"fromDate":"2024-03-10",
"toDate":"2024-03-14",
"types":["duties","initiative"]
} }
*/
This diff is collapsed.
...@@ -6,10 +6,12 @@ ...@@ -6,10 +6,12 @@
"@reduxjs/toolkit": "^2.2.1", "@reduxjs/toolkit": "^2.2.1",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^14.5.2",
"axios": "^1.6.7", "axios": "^1.6.7",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.18.3", "express": "^4.18.3",
"jspdf": "^2.5.1",
"jspdf-autotable": "^3.8.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongodb": "^6.5.0", "mongodb": "^6.5.0",
...@@ -50,6 +52,7 @@ ...@@ -50,6 +52,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.24.3",
"tailwindcss": "^3.4.1" "tailwindcss": "^3.4.1"
} }
} }
public/favicon.PNG

1.25 KB | W: | H:

public/favicon.PNG

2.38 KB | W: | H:

public/favicon.PNG
public/favicon.PNG
public/favicon.PNG
public/favicon.PNG
  • 2-up
  • Swipe
  • Onion skin
{ {
"short_name": "React App", "short_name": "Scorecard",
"name": "Create React App Sample", "name": "Nisum ScoreCard",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon1.png",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/png"
}, },
{ {
"src": "logo192.png", "src": "logo192.png",
......
{
"presets": ["@babel/preset-env"]
}
\ No newline at end of file
...@@ -6,6 +6,7 @@ import Viewreportee from './pages/viewReportee'; ...@@ -6,6 +6,7 @@ import Viewreportee from './pages/viewReportee';
import './App.css'; import './App.css';
import PageNotFound from './pages/pagenotfound/PageNotFound'; import PageNotFound from './pages/pagenotfound/PageNotFound';
import Exporttable from './pages/reportexport' import Exporttable from './pages/reportexport'
function App() { function App() {
return ( return (
<BrowserRouter> <BrowserRouter>
......
...@@ -3,6 +3,6 @@ import App from './App'; ...@@ -3,6 +3,6 @@ import App from './App';
test('renders learn react link', () => { test('renders learn react link', () => {
render(<App />); render(<App />);
const linkElement = screen.getByText(/learn react/i); // const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument(); // expect(linkElement).toBeInTheDocument();
}); });
import React from "react";
function DashboardIcon() {
return (
<svg
className="size-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
);
}
export default DashboardIcon;
import React from 'react'
function DownloadIcon() {
return (
<svg width="25px" height="25px" viewBox="0 0 24 24" fill="none" className='ml-2'>
<path d="M12 7L12 14M12 14L15 11M12 14L9 11" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M16 17H12H8" stroke="white" strokeWidth="1.5" strokeLinecap="round"/>
<path d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12Z" stroke="white" strokeWidth="1.5"/>
</svg>
)
}
export default DownloadIcon
import React from "react";
function ReportsIcon() {
return (
<svg
className="size-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
);
}
export default ReportsIcon;
...@@ -12,7 +12,7 @@ const PaginationComponent = ({ currentPage, totalPages, onPageChange }) => { ...@@ -12,7 +12,7 @@ const PaginationComponent = ({ currentPage, totalPages, onPageChange }) => {
return ( return (
<nav className="flex justify-center my-2"> <nav className="flex justify-center my-2">
<ul className="pagination flex"> <ul className="pagination flex">
<button disabled={currentPage === 1} onClick={() => onPageChange(currentPage-1)} className='mr-2 hover:bg-blue-400 border rounded disabled:bg-gray-200'><LeftIcon /></button> <button disabled={currentPage === 1} onClick={() => onPageChange(currentPage-1)} className={`ml-2 hover:bg-blue-400 border-0 bg-transparent rounded ${currentPage===1 ? "hidden":null}`}><LeftIcon /></button>
{pageNumbers.map(number => ( {pageNumbers.map(number => (
<li key={number} className={`page-item ${number === currentPage ? 'active' : ''}` }> <li key={number} className={`page-item ${number === currentPage ? 'active' : ''}` }>
<button onClick={() => onPageChange(number)} className=" w-[22px] font-bold h-[22px] "> <button onClick={() => onPageChange(number)} className=" w-[22px] font-bold h-[22px] ">
...@@ -20,7 +20,7 @@ const PaginationComponent = ({ currentPage, totalPages, onPageChange }) => { ...@@ -20,7 +20,7 @@ const PaginationComponent = ({ currentPage, totalPages, onPageChange }) => {
</button> </button>
</li> </li>
))} ))}
<button disabled={currentPage === totalPages} onClick={() => onPageChange(currentPage+1)} className='ml-2 hover:bg-blue-400 border rounded disabled:bg-gray-200'><RightIcon/></button> <button disabled={currentPage === totalPages} onClick={() => onPageChange(currentPage+1)} className={`ml-2 hover:bg-blue-400 border-0 bg-transparent rounded ${currentPage===totalPages ? "hidden":null}`}><RightIcon/></button>
</ul> </ul>
</nav> </nav>
); );
......
...@@ -29,12 +29,12 @@ function Accordion({ title, data ,handleAddActivity,open,handleAccordian}) { ...@@ -29,12 +29,12 @@ function Accordion({ title, data ,handleAddActivity,open,handleAccordian}) {
// } // }
// },[userDetails,id]) // },[userDetails,id])
useEffect(()=>{ // useEffect(()=>{
if(reports?.length !==null){ // if(reports?.length !==null){
dispatch(calculateDefaultScore(reports)) // dispatch(calculateDefaultScore(reports))
dispatch(calculateInitiativeScore(reports)) // dispatch(calculateInitiativeScore(reports))
} // }
},[reports,id]) // },[reports,id])
function handleClick(){ function handleClick(){
handleAccordian(title) handleAccordian(title)
...@@ -46,7 +46,7 @@ function Accordion({ title, data ,handleAddActivity,open,handleAccordian}) { ...@@ -46,7 +46,7 @@ function Accordion({ title, data ,handleAddActivity,open,handleAccordian}) {
{ title: "Score", id: "score", render: (value) => <div className="w-[35px] px-3 bg-blue-400 rounded-full text-white font-bold text-center p-[4px]">{value}</div> }, { title: "Score", id: "score", render: (value) => <div className="w-[35px] px-3 bg-blue-400 rounded-full text-white font-bold text-center p-[4px]">{value}</div> },
{ title: "Comments", id: "comments", render:(value)=><span className="listData" title={value}>{value}</span>}, { title: "Comments", id: "comments", render:(value)=><span className="listData" title={value}>{value}</span>},
]; ];
if(loading && title =="Duties")return <Loading/> if(loading && title =="Duties")return <Loading/>
if(!loading){ if(!loading){
...@@ -59,7 +59,7 @@ function Accordion({ title, data ,handleAddActivity,open,handleAccordian}) { ...@@ -59,7 +59,7 @@ function Accordion({ title, data ,handleAddActivity,open,handleAccordian}) {
className="flex items-center rounded-lg w-full py-2 px-2 mt-4 font-medium rtl:text-right bg-white text-gray-500 border border-[#B7B7B7] focus:ring-4 hover:bg-gray-100 gap-3" data-accordion-target="#accordion-collapse-body-2" aria-expanded="false" aria-controls="accordion-collapse-body-2" > className="flex items-center rounded-lg w-full py-2 px-2 mt-4 font-medium rtl:text-right bg-white text-gray-500 border border-[#B7B7B7] focus:ring-4 hover:bg-gray-100 gap-3" data-accordion-target="#accordion-collapse-body-2" aria-expanded="false" aria-controls="accordion-collapse-body-2" >
<span className="w-1/2 text-start ms-2">{title}</span> <span className="w-1/2 text-start ms-2">{title}</span>
<span className="w-1/2 flex justify-between"> <span className="w-1/2 flex justify-between">
Average Score :{title === "Duties" ? defaultAvgScore : initiativeAvgScore} Score: {title === "Duties" ? defaultAvgScore : initiativeAvgScore}
<ModalButton type={`${title === "Duties" ? "duties" : "initiative"}`} handleAddActivity={handleAddActivity}/> <ModalButton type={`${title === "Duties" ? "duties" : "initiative"}`} handleAddActivity={handleAddActivity}/>
</span> </span>
<svg data-accordion-icon className="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6"> <svg data-accordion-icon className="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
......
...@@ -37,7 +37,7 @@ function Header({ isOpen }) { ...@@ -37,7 +37,7 @@ function Header({ isOpen }) {
}, []); }, []);
return ( return (
<div className="flex items-center justify-between py-5 px-10" > <div className="flex items-center justify-between py-5 px-10 fixed bg-white w-full" >
<Link to={"/dashboard"}><img src="/logo.png" /></Link> <Link to={"/dashboard"}><img src="/logo.png" /></Link>
<div className="flex items-center relative"> <div className="flex items-center relative">
<button ref={logoutRef} className=" -mt-1 text-2xl flex" onClick={() => setOpen(!open)}> <button ref={logoutRef} className=" -mt-1 text-2xl flex" onClick={() => setOpen(!open)}>
......
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { fetchReportees, setViewReportee, setCurrPage, setPagesCount } from "../../redux/reducers/reporteesSlice"; import { fetchReportees, setCurrPage, setPagesCount, setReporteeId } from "../../redux/reducers/reporteesSlice";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
...@@ -15,20 +15,19 @@ function LeftSidebar() { ...@@ -15,20 +15,19 @@ function LeftSidebar() {
useEffect(() => { useEffect(() => {
if(inputValue!==null){ if (inputValue !== null) {
const debounceTimeout = setTimeout(() => { const debounceTimeout = setTimeout(() => {
const data = { const data = {
reportees: userDetails.user.reportees, reportees: userDetails.user.reportees,
page: currPage, page: (inputValue === "") ? currPage : 1,
perPage: 10, perPage: 10,
searchText:inputValue searchText: inputValue
}; };
dispatch(fetchReportees(data)); dispatch(fetchReportees(data));
}, 1000); }, 1000);
return () => clearTimeout(debounceTimeout);
}
return () => clearTimeout(debounceTimeout);
}
}, [inputValue]); }, [inputValue]);
const handleChange = (event) => { const handleChange = (event) => {
...@@ -52,44 +51,50 @@ function LeftSidebar() { ...@@ -52,44 +51,50 @@ function LeftSidebar() {
return ( return (
<div className=" w-[33%] flex flex-col px-[5px]"> <div className=" w-[33%] flex flex-col px-[5px]">
<div className=" flex mt-3 items-center justify-between"> <div className=" flex mt-3 items-center">
<p className="text-xl text-blue-400 font-semibold pl-4"> <p className="text-xl text-blue-400 font-semibold pl-4">
Reportees Reportees
</p> </p>
<input <input
placeholder="Search" placeholder="Search"
type="text" type="text"
className="p-2 border rounded w-[160px] placeholder:text-[14px]" className="p-2 border rounded ml-[16px] placeholder:text-[14px]"
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
/> />
</div> </div>
{ {
(loading) ? <Loading /> : (loading) ? <Loading /> :
<div className="p-2 bg-[#E9EDEE] mt-4 max-h-[70vh] overflow-auto"> <div className="p-2 bg-[#E9EDEE] mt-4 max-h-[70vh] overflow-auto">
{reportees?.map(({ empName, score, empId }) => ( {(reportees.length) ? reportees?.map(({ empName, score, empId }) => (
<button onClick={() => dispatch(setViewReportee(empId))} <button onClick={() => dispatch(setReporteeId(empId))}
// to={`/viewreportee`} className={`flex rounded-lg items-center hover:bg-blue-400 hover:text-white bg-${viewReportee?.empId == empId ? "blue-400 text-white" : "white"
className={`flex items-center hover:bg-blue-400 hover:text-white bg-${viewReportee?.empId == empId ? "blue-400 text-white" : "white"
} p-2 justify-between mb-1 w-full`} } p-2 justify-between mb-1 w-full`}
key={empId} key={empId}
> >
<p className="w-[80%] text-left">{empName}</p> <p className="w-[80%] text-left">{empName}</p>
<p className={`w-[30px] h-[30px] rounded-full flex items-center text-white justify-center ${scoreColor(score)}`}> <p className={`w-[30px] h-[30px] rounded-full flex items-center text-white justify-center ${scoreColor(score)}`}>
{score} {score}
</p> </p>
</button> </button>
))} )) : <div className="w-full h-full">
<p className="text-center align-middle text-blue-500 font-bold">No Records Found</p>
</div>
}
</div> </div>
} }
<div> <div>
<PaginationComponent {
currentPage={currPage} reportees.length > 0 && pagesCount > 1 && (
totalPages={pagesCount} <PaginationComponent
onPageChange={handlePageChange} currentPage={currPage}
/> totalPages={pagesCount}
onPageChange={handlePageChange}
/>
)
}
</div> </div>
</div> </div>
); );
......
...@@ -7,16 +7,16 @@ import { v4 as uuidv4 } from 'uuid'; ...@@ -7,16 +7,16 @@ import { v4 as uuidv4 } from 'uuid';
export default function MyModal({ visible, onClose, type, handleAddActivity }) { export default function MyModal({ visible, onClose, type, handleAddActivity }) {
const {user} = useSelector((state) => state.userDetails) const { user } = useSelector((state) => state.userDetails)
const [activitiesList, setActivitiesList] = useState([]) const [activitiesList, setActivitiesList] = useState([])
const [enableSubmit, setEnableSubmit] = useState(false) const [enableSubmit, setEnableSubmit] = useState(false)
const [activityData, setActivityData] = useState({ aName: "",ratedBy:"", aId: "", type: type, score: 0, comments: "" }) const [activityData, setActivityData] = useState({ aName: "", ratedBy: "", aId: "", type: type, score: 0, comments: "" })
const [activityType, setActivtyType] = useState("") const [activityType, setActivtyType] = useState("")
const [showCustActivity, setShowActivity] = useState(false); const [showCustActivity, setShowActivity] = useState(false);
const [modalLoading, setModalLoading] = useState(true) const [modalLoading, setModalLoading] = useState(true)
const [scoreRender, setScoreRender] = useState([]); const [scoreRender, setScoreRender] = useState([]);
const [showScore, setShowScore] = useState(false) const [showScore, setShowScore] = useState(false)
const [disableAppreciate,setDisableAppreciate]=useState(false) const [disableAppreciate, setDisableAppreciate] = useState(false)
const getActivitysList = async (type) => { const getActivitysList = async (type) => {
const activities = await axios.get(`${base_url}/activities`) const activities = await axios.get(`${base_url}/activities`)
...@@ -26,7 +26,14 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) { ...@@ -26,7 +26,14 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) {
} }
const handleActivityName = (e) => { const handleActivityName = (e) => {
setActivityData({ ...activityData, aName: e.target.value, aId: e.target.options[e.target.selectedIndex].id }) if (e.target.value === "custom") {
setActivityData({ ...activityData, aName: "", aId: "" })
setShowActivity(true)
}
else {
setShowActivity(false)
setActivityData({ ...activityData, aName: e.target.value, aId: e.target.options[e.target.selectedIndex].id })
}
} }
const handleCustumActivity = (e) => { const handleCustumActivity = (e) => {
...@@ -39,9 +46,9 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) { ...@@ -39,9 +46,9 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) {
setActivityData({ ...activityData, score: Number(value) }) setActivityData({ ...activityData, score: Number(value) })
} }
const handlePerformance=(value)=> { const handlePerformance = (value) => {
let appreciateScores = [1, 2, 3, 4, 5] let appreciateScores = [1, 2, 3, 4, 5]
let depreciateScores = [ -1, -2, -3, -4, -5] let depreciateScores = [-1, -2, -3, -4, -5]
if (value == 1) { if (value == 1) {
setActivityData({ ...activityData, score: 0 }) setActivityData({ ...activityData, score: 0 })
setScoreRender(appreciateScores) setScoreRender(appreciateScores)
...@@ -55,10 +62,11 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) { ...@@ -55,10 +62,11 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) {
} }
const handleComments = (e) => { const handleComments = (e) => {
setActivityData({ ...activityData, comments:e.target.value.trim() }) setActivityData({ ...activityData, comments: e.target.value.trim() })
} }
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault()
onClose() onClose()
setShowActivity(false) setShowActivity(false)
handleAddActivity(activityData) handleAddActivity(activityData)
...@@ -79,16 +87,16 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) { ...@@ -79,16 +87,16 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) {
setActivtyType(str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()) setActivtyType(str.charAt(0).toUpperCase() + str.slice(1).toLowerCase())
} }
useEffect(() => { useEffect(() => {
if(type==="duties"){ if (type === "duties") {
setDisableAppreciate(true); setDisableAppreciate(true);
}else{ } else {
setDisableAppreciate(false); setDisableAppreciate(false);
} }
SentenceCase(type) SentenceCase(type)
if (visible === false) { if (visible === false) {
setActivityData({ aName: "",ratedBy:"", aId: "", type: type, score: 0, comments: "" }) setActivityData({ aName: "", ratedBy: "", aId: "", type: type, score: 0, comments: "" })
} else { } else {
setActivityData({...activityData,ratedBy:user.empName}) setActivityData({ ...activityData, ratedBy: user.empName })
getActivitysList(type); getActivitysList(type);
setModalLoading(true) setModalLoading(true)
} }
...@@ -114,25 +122,19 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) { ...@@ -114,25 +122,19 @@ export default function MyModal({ visible, onClose, type, handleAddActivity }) {
<div> <div>
<form className=" p-2 max-w-sm mx-auto text-[12px]" onClick={(e) => e.stopPropagation()}> <form className=" p-2 max-w-sm mx-auto text-[12px]" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center my-5"> <div className="flex items-center my-5">
<label htmlFor="countries">SELECT ACTIVITY<span className="text-[15px]">*</span>: </label> <label htmlFor="countries">SELECT ACTIVITY<span className="text-[15px]">*</span>: </label>
<select disabled={showCustActivity} className="bg-gray-50 ml-2 w-6/12 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 " onChange={(e) => handleActivityName(e)} value={activityData.aName}> <select className="bg-gray-50 ml-2 w-6/12 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 " onChange={(e) => handleActivityName(e)} >
<option id="" value="">Select</option> <option id="" value="">Select</option>
{ {
activitiesList && activitiesList.map((activity) => <option className=" w-7/12" key={activity.aId} id={activity.aId} value={activity.aName}>{activity.aName}</option>) activitiesList && activitiesList.map((activity) => <option className=" w-7/12" key={activity.aId} id={activity.aId} value={activity.aName}>{activity.aName}</option>)
} }
<option value="custom" className={`${showCustActivity || type == "duties" && 'hidden'}`}>Add Activity</option>
</select> </select>
<button onClick={(e) => { handleCustBtn(e) }} className={`${showCustActivity && 'hidden'} bg-blue-400 ml-2 w-2/12 text-white py-1 rounded hover:scale-95 transition text-sm`}>Custom</button>
</div> </div>
<div className={`flex items-center ${!showCustActivity && 'hidden'}`}> <div className={`flex items-center ${!showCustActivity && 'hidden'}`}>
<label className={`font-medium mr-2`}>Custom Activity<span className="text-[15px]">*</span>:</label> <label className={`font-medium mr-2`}>Activity<span className="text-[15px]">*</span>:</label>
<input type="text" value={activityData.aName} placeholder="Enter Activity name" name="performance" className={`border border-gray-300 rounded p-2 `} onChange={(e) => handleCustumActivity(e)} /> <input type="text" value={activityData.aName} placeholder="Enter Activity name" name="performance" className={`border border-gray-300 rounded p-2 `} onChange={(e) => handleCustumActivity(e)} />
<button onClick={(e) => { handleCustBtn(e) }} className={`${!showCustActivity && 'hidden'} bg-blue-400 ml-2 w-2/12 text-white py-1 rounded hover:scale-95 transition text-sm`}>Close</button>
</div> </div>
<div className="flex items-center mb-4 "> <div className="flex items-center mb-4 ">
<label htmlFor="appreciate" className="font-medium">APPRECIATION<span className="text-[15px]">*</span>:</label> <label htmlFor="appreciate" className="font-medium">APPRECIATION<span className="text-[15px]">*</span>:</label>
<input id="appreciate" disabled={disableAppreciate} type="radio" value="appreciate" name="performance" className="w-4 h-4 m-3 text-blue-600 bg-gray-100 border-gray-300 " onChange={() => handlePerformance(1)} /> <input id="appreciate" disabled={disableAppreciate} type="radio" value="appreciate" name="performance" className="w-4 h-4 m-3 text-blue-600 bg-gray-100 border-gray-300 " onChange={() => handlePerformance(1)} />
......
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { useSelector } from "react-redux"; import SetWindowSize from '../../utils/SetWindowSize';
import DashboardIcon from '../../assets/icons/dashboardIcon';
import ReportsIcon from '../../assets/icons/reportsIcon';
const menus = [
{title: "Dashboard", path: '/dashboard', selectPaths: ['dashboard', 'viewreportee'], icon: <DashboardIcon/> },
{title: "Reports", path: '/reportees', selectPaths:['reportees'], icon: <ReportsIcon />}
]
function Sidebar() { function Sidebar() {
const user = useSelector((state) => state.userDetails.user); const url = window.location.href;
const [windowWidth, windowHeight] = SetWindowSize();
const selected = url.split('/').at(-1)
return ( return (
<div className="w-[20%] flex items-center flex-col"> <div className="w-[20%] flex items-center flex-col overflow-auto" style={{ height: `calc(${windowHeight}px - 87px)` }}>
<nav <nav
className="hs-accordion-group p-6 w-full flex flex-col flex-wrap" className="hs-accordion-group p-6 w-full flex flex-col flex-wrap"
data-hs-accordion-always-open data-hs-accordion-always-open
> >
<ul className="space-y-1.5"> <ul className="space-y-1.5">
<li> {
<Link menus.map((menu) => (
className={`flex items-center gap-x-3.5 py-2 px-2.5 bg-gray-100 text-sm text-slate-700 rounded-lg hover:bg-gray-100 `} <li key={menu.path}>
to={`/dashboard`} <Link
> className={`flex items-center ${menu.selectPaths.includes(selected) && 'bg-gray-100'} gap-x-3.5 py-2 px-2.5 text-sm text-slate-700 rounded-lg hover:bg-gray-100 `}
<svg to={menu.path}
className="size-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
Dashboard
</Link>
</li>
<li>
<Link
className={`flex items-center gap-x-3.5 py-2 px-2.5 text-sm text-slate-700 rounded-lg hover:bg-gray-100 `} to={`/reportees`}
>
<svg
className="size-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
> >
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /> <span>{menu.icon}</span>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /> {menu.title}
</svg> </Link>
Reports </li>
</Link> ))
</li> }
</ul> </ul>
</nav> </nav>
</div> </div>
......
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import { useSelector, useDispatch} from "react-redux";
import {setSortKey, setSortOrder} from '../../redux/reducers/reporteesSlice';
import Loading from "../loading Component/Loading"; import Loading from "../loading Component/Loading";
import SortButton from "../sortButton"; import SortButton from "../sortButton";
function Table({headers, data,loading, maxHeight}) { function Table({headers, data,loading, handleSorting }) {
const [sortedData, setSortedData] = useState([]); const dispatch = useDispatch();
const [sortKey, setSortKey] = useState(null); const {sortKey, sortOrder} = useSelector((state) => state.reportees);
const [sortOrder, setSortOrder] = useState('asc'); // const [sortedData, setSortedData] = useState([]);
// const [sortKey, setSortKey] = useState(null);
// const [sortOrder, setSortOrder] = useState('asc');
useEffect(() => { // useEffect(() => {
if(sortKey) { // if(sortKey) {
setSortKey(null); // setSortKey(null);
} // }
setSortedData(data); // setSortedData(data);
}, [data]); // }, [data]);
// useEffect(() => {
// if(sortKey) {
// }
// }, [sortKey, sortOrder])
const handleSort = (key) => { const handleSort = (key) => {
const order = key === sortKey ? (sortOrder === 'asc' ? 'desc' : 'asc') : 'asc';
const order = key === sortKey ? (sortOrder === 'asc' ? 'desc' : 'asc') : 'asc';
if(sortOrder === 'desc' && key === sortKey) { if(sortOrder === 'desc' && key === sortKey) {
setSortedData(data); dispatch(setSortKey(null))
setSortKey(null); dispatch(setSortOrder('asc'));
setSortOrder('asc'); handleSorting(null, order)
} else { } else {
const sorted = [...data].sort((a, b) => { dispatch(setSortKey(key));
if (typeof a[key] === 'string') { dispatch(setSortOrder(order));
return order === 'asc' ? a[key].localeCompare(b[key]) : b[key].localeCompare(a[key]); handleSorting(key, order)
}
return order === 'asc' ? a[key] - b[key] : b[key] - a[key];
});
setSortedData(sorted);
setSortKey(key);
setSortOrder(order);
} }
// if(sortOrder === 'desc' && key === sortKey) {
// setSortedData(data);
// setSortKey(null);
// setSortOrder('asc');
// } else {
// const sorted = [...data].sort((a, b) => {
// if (typeof a[key] === 'string') {
// return order === 'asc' ? a[key].localeCompare(b[key]) : b[key].localeCompare(a[key]);
// }
// return order === 'asc' ? a[key] - b[key] : b[key] - a[key];
// });
// setSortedData(sorted);
// setSortKey(key);
// setSortOrder(order);
// }
}; };
...@@ -58,7 +79,7 @@ function Table({headers, data,loading, maxHeight}) { ...@@ -58,7 +79,7 @@ function Table({headers, data,loading, maxHeight}) {
{ {
(data?.length)?<tbody > (data?.length)?<tbody >
{ {
sortedData?.map((item, index) => ( data?.map((item, index) => (
<tr key={item.id} className="bg-white hover:bg-gray-300 " > <tr key={item.id} className="bg-white hover:bg-gray-300 " >
{ {
headers?.map(({render, id}) => ( headers?.map(({render, id}) => (
...@@ -75,15 +96,11 @@ function Table({headers, data,loading, maxHeight}) { ...@@ -75,15 +96,11 @@ function Table({headers, data,loading, maxHeight}) {
</table> </table>
{ {
(!data?.length)?<div className="w-full h-full"> (!data?.length)?<div className="w-full h-full">
<p className="text-center align-middle pt-14 pb-14 text-blue-500 font-bold">No records to display</p> <p className="text-center align-middle pt-14 pb-14 text-blue-500 font-bold">No Records Found</p>
</div>:null </div>:null
} }
</div> </div>
); );
// else
// return <div className="w-full h-full">
// <p className="text-center align-middle pt-14 pb-14 text-blue-500 font-bold">No records to display</p>
// </div>
} }
export default Table; export default Table;
...@@ -23,6 +23,23 @@ code { ...@@ -23,6 +23,23 @@ code {
display: inline-block; display: inline-block;
} }
.childDiv {
max-height: calc(100vh - 85px); .loading {
width: 100%;
height: 100%;
content: '';
top: 50%;
left: 50%;
width: 20px;
height: 20px;
border: 3px solid #fff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
} }
@keyframes spin {
to {
transform: rotate(360deg);
}
}
\ No newline at end of file
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { fetchReportees, setViewReportee, setCurrPage, setPagesCount } from "../../redux/reducers/reporteesSlice"; import { fetchReportees, setViewReportee, setCurrPage, setPagesCount, setReporteeId } from "../../redux/reducers/reporteesSlice";
import Table from '../../components/table'; import Table from '../../components/table';
import RightArrowIcon from '../../assets/icons/rightArrowIcon'; import RightArrowIcon from '../../assets/icons/rightArrowIcon';
import { scoreColor } from '../../utils/commonFunctions'; import { scoreColor } from '../../utils/commonFunctions';
...@@ -10,32 +10,42 @@ import PaginationComponent from "../../components/Pagenation/Pagenation"; ...@@ -10,32 +10,42 @@ import PaginationComponent from "../../components/Pagenation/Pagenation";
function Dashboard() { function Dashboard() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const { reportees, loading, totalCount, currPage, pagesCount} = useSelector((state) => state.reportees); const { reportees, loading, totalCount, currPage, pagesCount, sortKey, sortOrder} = useSelector((state) => state.reportees);
const userDetails = useSelector((state) => state.userDetails); const userDetails = useSelector((state) => state.userDetails);
const [reporteIds, setReporteIds] = useState([]); const [reporteIds, setReporteIds] = useState([]);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState(null);
// userDetails.user.reportees || []; // userDetails.user.reportees || [];
const handlePageChange = (currPage) => { const handlePageChange = (currPage) => {
let data = { let data = {
reportees: userDetails.user.reportees, reportees: userDetails.user.reportees,
page: currPage, page: currPage,
perPage: 10 perPage: 10,
sort: sortKey ? {type:sortKey, order: sortOrder === "asc" ? 1 : -1} : {}
} }
dispatch(setCurrPage(currPage)) dispatch(setCurrPage(currPage))
dispatch(fetchReportees(data)) dispatch(fetchReportees(data))
} }
const handleSort = (key, order) => {
let data = {
reportees: userDetails.user.reportees,
page: currPage,
perPage: 10,
sort: key ? {type:key, order: order === "asc" ? 1 : -1} : {}
}
dispatch(fetchReportees(data))
}
useEffect(() => { useEffect(() => {
dispatch(setPagesCount(Math.ceil((totalCount) / (10)))) dispatch(setPagesCount(Math.ceil((totalCount) / (10))))
}, [totalCount]) }, [totalCount])
useEffect(() => { useEffect(() => {
if (reporteIds.length > 0) { if (reporteIds.length > 0 ) {
const data = { const data = {
reportees: userDetails.user.reportees, reportees: userDetails.user.reportees,
// sort: { type: "empId", order: 1 },
page: currPage, page: currPage,
perPage: 10 perPage: 10
}; };
...@@ -46,24 +56,27 @@ function Dashboard() { ...@@ -46,24 +56,27 @@ function Dashboard() {
useEffect(() => { useEffect(() => {
if (userDetails.user) { if (userDetails.user) {
setReporteIds(userDetails.user.reportees) setReporteIds(userDetails.user.reportees)
navigate("/dashboard") // navigate("/dashboard")
} else { } else {
navigate("/") navigate("/")
} }
}, [userDetails]); }, [userDetails]);
useEffect(() => { useEffect(() => {
if(inputValue!==null){
const debounceTimeout = setTimeout(() => { const debounceTimeout = setTimeout(() => {
const data = { const data = {
reportees: userDetails.user.reportees, reportees: userDetails.user.reportees,
page: currPage, page: 1,
perPage: 10, perPage: 10,
searchText:inputValue searchText:inputValue
}; };
dispatch(fetchReportees(data)); dispatch(fetchReportees(data));
}, 1000); }, 1000);
return () => clearTimeout(debounceTimeout); return () => clearTimeout(debounceTimeout);
}
// return () => clearTimeout(debounceTimeout);
}, [inputValue]); }, [inputValue]);
const handleChange = (event) => { const handleChange = (event) => {
...@@ -104,7 +117,7 @@ function Dashboard() { ...@@ -104,7 +117,7 @@ function Dashboard() {
title: "Action", title: "Action",
id: "empId", id: "empId",
render: (value) => <Link to={`/viewreportee`}> render: (value) => <Link to={`/viewreportee`}>
<button className="bg-blue-400 text-white rounded-md px-2 py-1 flex items-center justify-center w-[40px]" onClick={()=>dispatch(setViewReportee(value))}> <button className="bg-blue-400 text-white rounded-md px-2 py-1 flex items-center justify-center w-[40px]" onClick={()=>dispatch(setReporteeId(value))}>
<RightArrowIcon /> <RightArrowIcon />
</button> </button>
</Link> </Link>
...@@ -119,12 +132,11 @@ function Dashboard() { ...@@ -119,12 +132,11 @@ function Dashboard() {
<label>Search :</label> <label>Search :</label>
<input placeholder="Name/Id/Designation/Role" value={inputValue} onChange={handleChange} type="text" className="p-1 px-2 border rounded ml-2 placeholder:text-[14px]"/> <input placeholder="Name/Id/Designation/Role" value={inputValue} onChange={handleChange} type="text" className="p-1 px-2 border rounded ml-2 placeholder:text-[14px]"/>
</div> </div>
<Table headers={headers} data={reportees} loading={loading} maxHeight={88} /> <Table headers={headers} data={reportees} loading={loading} handleSorting={handleSort}/>
<div className=""> <div className="">
{reportees && ( {reportees.length>0 && pagesCount>1 && (
<div className="flex justify-center mt-2"> <div className="flex justify-center mt-2">
{/* <div className="text-blue-500">Total Results: {pagesCount}</div> */}
{pagesCount >= 1 && ( {pagesCount >= 1 && (
<PaginationComponent <PaginationComponent
currentPage={currPage} currentPage={currPage}
......
...@@ -4,7 +4,6 @@ import axios from 'axios'; ...@@ -4,7 +4,6 @@ import axios from 'axios';
import { base_url } from "../../utils/constants"; import { base_url } from "../../utils/constants";
import { loginUser } from "../../redux/reducers/userSlice"; import { loginUser } from "../../redux/reducers/userSlice";
import {useDispatch,useSelector} from 'react-redux' import {useDispatch,useSelector} from 'react-redux'
import Loading from '../../components/loading Component/Loading'
function Home() { function Home() {
const inputRef = useRef(null); const inputRef = useRef(null);
......
...@@ -2,9 +2,12 @@ import React, { useState } from 'react' ...@@ -2,9 +2,12 @@ import React, { useState } from 'react'
import Header from '../../components/header'; import Header from '../../components/header';
import Sidebar from '../../components/sidebar'; import Sidebar from '../../components/sidebar';
import LeftSidebar from '../../components/leftSidebar'; import LeftSidebar from '../../components/leftSidebar';
import SetWindowSize from '../../utils/SetWindowSize';
function Layout({children}) { function Layout({children}) {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false);
const [windowWidth, windowHeight] = SetWindowSize();
const handleLogoutOpen=()=>{ const handleLogoutOpen=()=>{
setIsOpen(false) setIsOpen(false)
} }
...@@ -12,9 +15,9 @@ function Layout({children}) { ...@@ -12,9 +15,9 @@ function Layout({children}) {
return ( return (
<div className='max-h-[84vh]' onClick={handleLogoutOpen}> <div className='max-h-[84vh]' onClick={handleLogoutOpen}>
<Header isOpen={isOpen} /> <Header isOpen={isOpen} />
<div className="flex"> <div className="flex pt-[85px]">
<Sidebar/> <Sidebar/>
<div className="bg-[#E9EDEE] w-full" style={{height:"88vh"}}> <div className="bg-[#E9EDEE] w-full overflow-auto" style={{ height: `calc(${windowHeight}px - 87px)` }}>
{children} {children}
</div> </div>
{url.includes('/viewreportee') && <LeftSidebar/>} {url.includes('/viewreportee') && <LeftSidebar/>}
......
This diff is collapsed.
export const styles = {
genarateReportContainer: "overflow-auto sm:rounded-lg p-4 bg-[#E9EDEE] ",
textBlueHeading: "text-blue-800 py-3 pl-2 text-center fond-bold text-2xl",
formContainer: "p-2 text-[12px] mb-4",
flexContainer: "flex items-center justify-between",
flexItemsCenter: "flex items-center",
selectEmployeeDropdown:
"bg-gray-50 text-balance rounded-lg rounded-md ml-2 border border-gray-300 text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 block p-2.5 ",
selectDropdown:"bg-gray-50 text-balance rounded-lg ml-2 border border-gray-300 text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 block p-2.5 w-[235px]",
downloadButton:
"px-3 py-2 ml-5 min-w-[100px] disabled:bg-gray-400 h-[40px] bg-blue-500 font-semibold text-white rounded-md flex items-center justify-center",
Norecords: "text-center align-middle pt-14 pb-14 text-blue-500 font-bold",
};
...@@ -4,7 +4,7 @@ import { useParams, useNavigate } from "react-router"; ...@@ -4,7 +4,7 @@ import { useParams, useNavigate } from "react-router";
import { base_url } from "../../utils/constants"; import { base_url } from "../../utils/constants";
import axios from 'axios'; import axios from 'axios';
import { fetchReportees,setViewReportee } from "../../redux/reducers/reporteesSlice"; import { fetchReportees,setViewReportee } from "../../redux/reducers/reporteesSlice";
import {fetchReporteeActivities} from '../../redux/reducers/viewreporteeSlice' import {fetchReporteeActivities, fetchActivitiesAvg, } from '../../redux/reducers/viewreporteeSlice'
import Accordion from "../../components/accordion"; import Accordion from "../../components/accordion";
import {scoreColor} from '../../utils/commonFunctions'; import {scoreColor} from '../../utils/commonFunctions';
...@@ -12,35 +12,31 @@ import {scoreColor} from '../../utils/commonFunctions'; ...@@ -12,35 +12,31 @@ import {scoreColor} from '../../utils/commonFunctions';
function Viewreportee() { function Viewreportee() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const {reportees, viewReportee } = useSelector((state) => state.reportees); const {reportees, viewReportee,currPage, reporteeId } = useSelector((state) => state.reportees);
const user = useSelector((state) => state.userDetails.user) const user = useSelector((state) => state.userDetails.user)
const { reports, loading, error } = useSelector((state) => state.reports); const { reports, loading, error, dutiesReports, initiativeReports } = useSelector((state) => state.reports);
const [open, setOpen] = useState({ "accordianOne": false, "accordianTwo": false }); const [open, setOpen] = useState({ "accordianOne": false, "accordianTwo": false });
const fetchActivities = (type) => {
const data ={
/*Example post data empId:viewReportee?.empId,
{ types:[type],
"empId":41689, page: 1,
"fromDate":"2024-03-10", perPage: 5
"toDate":"2024-03-11"
}
*/
const activities = useMemo(() => {
if (reports) {
const filtered = Object.groupBy(reports, ({ type }) => type);
return filtered;
} }
}, [reports, viewReportee]); dispatch(fetchReporteeActivities(data))
}
const handleAccordian = (value) => { const handleAccordian = (value) => {
switch (value) { switch (value) {
case "Duties": case "Duties":
setOpen({ ...open, "accordianOne": !open["accordianOne"], "accordianTwo": false }); setOpen({ ...open, "accordianOne": !open["accordianOne"], "accordianTwo": false });
fetchActivities('duties')
break; break;
case "Initiatives": case "Initiatives":
setOpen({ ...open, "accordianOne": false, "accordianTwo": !open["accordianTwo"] }); setOpen({ ...open, "accordianOne": false, "accordianTwo": !open["accordianTwo"] });
fetchActivities('initiative')
break; break;
default: default:
setOpen({ "accordianOne": false, "accordianTwo": false }); setOpen({ "accordianOne": false, "accordianTwo": false });
...@@ -51,13 +47,17 @@ function Viewreportee() { ...@@ -51,13 +47,17 @@ function Viewreportee() {
if (user) { if (user) {
const data = { const data = {
reportees: user.reportees, reportees: user.reportees,
sort: { type: "empId", order: 1 }, page: currPage,
page: 1,
perPage: 10, perPage: 10,
}; };
dispatch(fetchReportees(data)) dispatch(fetchReportees(data))
} }
}
const fetchViewReporteeData=async(empId)=>{
const response= await axios.get(`${base_url}/employee/${empId}`)
const data=await response.data
dispatch(setViewReportee(data))
} }
const handleAddActivity = async (activityData) => { const handleAddActivity = async (activityData) => {
...@@ -68,66 +68,82 @@ function Viewreportee() { ...@@ -68,66 +68,82 @@ function Viewreportee() {
} }
await axios.post(`${base_url}/createActivity`, newData) await axios.post(`${base_url}/createActivity`, newData)
.then(async (result) => { .then(async (result) => {
fetchLatestReporteesData() fetchLatestReporteesData();
fetchActivities(activityData?.type)
fetchViewReporteeData(reporteeId)
dispatch(fetchActivitiesAvg({empId:reporteeId, types:["duties", "initiative"]}))
}) })
} else { } else {
alert("Please login") alert("Please login")
} }
} }
useEffect(()=>{
if(reportees.length>0 && viewReportee !== null)
dispatch(fetchReporteeActivities({empId:viewReportee?.empId}))
},[reportees,viewReportee])
// useEffect(()=>{
// if(reportees.length){
// dispatch(setViewReportee(viewReportee?.empId))
// }
// },[reportees])
useEffect(() => {
if(reporteeId) {
fetchViewReporteeData(reporteeId)
dispatch(fetchActivitiesAvg({empId:reporteeId, types:["duties", "initiative"]}))
}
}, [reporteeId])
useEffect(() => { useEffect(() => {
if (user) { if (user) {
navigate(`/viewreportee`) // navigate(`/viewreportee`)
setOpen({ "accordianOne": false, "accordianTwo": false }) setOpen({ "accordianOne": false, "accordianTwo": false })
} else { } else {
navigate("/") navigate("/")
} }
}, []); }, []);
if ( reportees.length && viewReportee) if (viewReportee!==null)
return ( return (
<div className="p-4" > <div className="p-4" >
<div className="bg-white p-3 rounded-md"> <div className="bg-white p-3 rounded-md">
<div className="flex justify-between"> <div className="flex justify-between">
{/* <img src="/generic-male-avatar-rectangular.jpg" width="100px" height="100px" /> */} {/* <div className="my-1">
<div className="my-1">
<p> <p>
<span className="font-medium">Employee Name : </span> {viewReportee?.empName} <span className="font-medium">Employee Name: </span> {viewReportee?.empName}
</p> </p>
<p> <p>
<span className="font-medium">Designation : </span> {viewReportee?.designation} <span className="font-medium">Designation: </span> {viewReportee?.designation}
</p> </p>
{/* <p> </div> */}
<span className="font-medium">Email Id: </span> {viewReportee?.empEmail} <div className="flex items-center">
</p> */} <div>
<p className="font-medium mb-2">
Employee Name
</p>
<p className="font-medium">
Employee Id
</p>
</div>
<div>
<p className="mb-2"><span className="font-medium">:</span> {viewReportee?.empName}</p>
<p><span className="font-medium">:</span> {viewReportee?.empId}</p>
</div>
</div> </div>
<div className="my-1"> {/* <div className="my-1">
<p> <p>
<span className="font-medium">Role : </span> {viewReportee?.techStack} <span className="font-medium">Role: </span> {viewReportee?.techStack}
</p> </p>
<p> <p>
<span className="font-medium">Employee Id: </span> {viewReportee?.empId} <span className="font-medium">Employee Id: </span> {viewReportee?.empId}
</p> </p>
{/* <p> </div> */}
<span className="font-medium">Total Score : </span> {viewReportee?.score} <div className="flex items-center">
</p> */} <div>
{/* <p> <p className="font-medium mb-2">
<span className="font-medium">Allocated To : </span> {viewReportee?.project} Designation
</p> */} </p>
<p className="font-medium">
Role
</p>
</div>
<div>
<p className="mb-2"><span className="font-medium">:</span> {viewReportee?.designation}</p>
<p><span className="font-medium">:</span> {viewReportee?.techStack}</p>
</div>
</div> </div>
<div className="flex flex-col justify-center items-center"> <div className="flex flex-col justify-center items-center">
<div className={`w-[40px] h-[40px] rounded-full flex items-center text-white justify-center ${scoreColor(viewReportee?.score)}`}> <div className={`w-[40px] h-[40px] rounded-full flex items-center text-white justify-center ${scoreColor(viewReportee?.score)}`}>
...@@ -137,15 +153,14 @@ function Viewreportee() { ...@@ -137,15 +153,14 @@ function Viewreportee() {
<span className="text-blue-400 font-semibold">Total Score</span> <span className="text-blue-400 font-semibold">Total Score</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className=""> <div className="">
<div className=""> <div className="">
<Accordion title="Duties" open={open.accordianOne} handleAccordian={handleAccordian} data={activities?.duties} handleAddActivity={handleAddActivity} /> <Accordion title="Duties" open={open.accordianOne} handleAccordian={handleAccordian} data={dutiesReports} handleAddActivity={handleAddActivity} />
</div> </div>
<div className=""> <div className="">
<Accordion title="Initiatives" open={open.accordianTwo} handleAccordian={handleAccordian} data={activities?.initiative} handleAddActivity={handleAddActivity} /> <Accordion title="Initiatives" open={open.accordianTwo} handleAccordian={handleAccordian} data={initiativeReports} handleAddActivity={handleAddActivity} />
</div> </div>
</div> </div>
</div> </div>
......
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { base_url } from "../../utils/constants"; import { base_url } from "../../utils/constants";
import axios from "axios"; import axios from "axios";
const initialState = { const initialState = {
totalReporteesData: [], // totalReporteesData: [],
activitiesData:null, activitiesData:[],
loading: false, loading: false,
error: null, error: null,
fromDate: null,
toDate: null,
}; };
export const fetchReportstableData = createAsyncThunk("getreportees", async (data) => {
return await axios.post(`${base_url}/getreportees`, data) export const fetchReportesActivitiesData = createAsyncThunk("gettotalactivities", async (data) => {
return await axios.post(`${base_url}/getActivities`, data)
.then((response) => response.data); .then((response) => response.data);
}); });
const exporttableSlice = createSlice({ const exporttableSlice = createSlice({
name: "totalReportees", name: "totalReportees",
initialState, initialState,
reducers: { reducers: {
resetActivitiesData: (state) => {
state.activitiesData = []
},
resetReporteesTableData:() => { resetReporteesTableData:() => {
return initialState return initialState
}, },
setPastMonth: (state) => {
const toDate = new Date();
const fromDate = new Date();
fromDate.setMonth(fromDate.getMonth() - 1);
state.fromDate = fromDate;
state.toDate = toDate;
console.log(fromDate,toDate)
},
setPastTwoMonths: (state) => {
const toDate = new Date();
const fromDate = new Date();
fromDate.setMonth(fromDate.getMonth() - 3);
state.fromDate = fromDate;
state.toDate = toDate;
console.log(fromDate,toDate)
},
setPastsixMonths: (state) => {
const toDate = new Date();
const fromDate = new Date();
fromDate.setMonth(fromDate.getMonth() - 6);
state.fromDate = fromDate;
state.toDate = toDate;
console.log(fromDate,toDate)
},
setPasttwelvemonths: (state) => {
const toDate = new Date();
const fromDate = new Date();
fromDate.setMonth(fromDate.getMonth() - 12);
state.fromDate = fromDate;
state.toDate = toDate;
console.log(fromDate,toDate)
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(fetchReportstableData.pending, (state) => {
builder.addCase(fetchReportesActivitiesData.pending, (state) => {
state.loading = true; state.loading = true;
state.error = "pending"; state.error = "pending";
}); });
builder.addCase(fetchReportstableData.fulfilled, (state, action) => { builder.addCase(fetchReportesActivitiesData.fulfilled, (state, action) => {
// console.log(action.payload.activities)
state.loading = false; state.loading = false;
state.totalReporteesData = action.payload.data; state.activitiesData = action.payload.activities ;
state.error = ""; state.error = "";
}); });
builder.addCase(fetchReportstableData.rejected, (state, action) => { builder.addCase(fetchReportesActivitiesData.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.totalReporteesData = null; state.activitiesData = null;
state.error = action.error || "Something went wrong!"; state.error = action.error || "Something went wrong!";
}); });
}, },
}); });
export const {resetReporteesTableData,setPastMonth,setPastTwoMonths,setPastsixMonths,setPasttwelvemonths} = exporttableSlice.actions; export const {resetReporteesTableData, resetActivitiesData} = exporttableSlice.actions;
export default exporttableSlice.reducer; export default exporttableSlice.reducer;
...@@ -4,12 +4,15 @@ import axios from "axios"; ...@@ -4,12 +4,15 @@ import axios from "axios";
const initialState = { const initialState = {
reportees: [], reportees: [],
reporteeId: null,
viewReportee:null, viewReportee:null,
totalCount:0, totalCount:0,
loading: false, loading: false,
error: null, error: null,
currPage:1, currPage:1,
pagesCount:1 pagesCount:1,
sortKey: null,
sortOrder: 'asc'
}; };
export const fetchReportees = createAsyncThunk("getreportees", async (data) => { export const fetchReportees = createAsyncThunk("getreportees", async (data) => {
...@@ -25,24 +28,25 @@ const reporteesSlice = createSlice({ ...@@ -25,24 +28,25 @@ const reporteesSlice = createSlice({
return initialState return initialState
}, },
setViewReportee:(state,action)=>{ setViewReportee:(state,action)=>{
const reportee=state.reportees.find((reportee)=>reportee.empId==action.payload)
if(!reportee){
return { return {
...state, ...state,
viewReportee: null viewReportee: action.payload
} }
} else { },
return { setReporteeId: (state, action) => {
...state, state.reporteeId = action.payload
viewReportee: reportee
}
}
}, },
setCurrPage: (state, action) => { setCurrPage: (state, action) => {
state.currPage = action.payload state.currPage = action.payload
}, },
setPagesCount: (state, action) => { setPagesCount: (state, action) => {
state.pagesCount = action.payload state.pagesCount = action.payload
},
setSortKey: (state, action) => {
state.sortKey = action.payload
},
setSortOrder: (state, action) => {
state.sortOrder = action.payload
} }
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
...@@ -64,6 +68,6 @@ const reporteesSlice = createSlice({ ...@@ -64,6 +68,6 @@ const reporteesSlice = createSlice({
}, },
}); });
export const {resetReportees,setViewReportee, setCurrPage, setPagesCount} = reporteesSlice.actions; export const {resetReportees,setViewReportee, setCurrPage, setPagesCount, setSortKey, setSortOrder, setReporteeId} = reporteesSlice.actions;
export default reporteesSlice.reducer; export default reporteesSlice.reducer;
...@@ -4,6 +4,8 @@ import axios from "axios"; ...@@ -4,6 +4,8 @@ import axios from "axios";
const initialState = { const initialState = {
reports: null, reports: null,
dutiesReports: null,
initiativeReports: null,
defaultAvgScore:0, defaultAvgScore:0,
initiativeAvgScore:0, initiativeAvgScore:0,
loading: false, loading: false,
...@@ -13,6 +15,12 @@ const initialState = { ...@@ -13,6 +15,12 @@ const initialState = {
export const fetchReporteeActivities = createAsyncThunk("getReports", async (data) => { export const fetchReporteeActivities = createAsyncThunk("getReports", async (data) => {
return await axios return await axios
.post(`${base_url}/getActivities`, data) .post(`${base_url}/getActivities`, data)
.then((response) => {return {data:response.data?.activities, type:data.types}});
});
export const fetchActivitiesAvg = createAsyncThunk("getActivities-avg", async (data) => {
return await axios
.post(`${base_url}/getActivities-avg`, data)
.then((response) => response.data); .then((response) => response.data);
}); });
...@@ -23,39 +31,32 @@ const reportSlice = createSlice({ ...@@ -23,39 +31,32 @@ const reportSlice = createSlice({
resetReports:() => { resetReports:() => {
return initialState return initialState
}, },
calculateDefaultScore:(state, action)=>{
if(action.payload===undefined){
return {...state,defaultAvgScore :0}
}else{
const dutiesItems = action.payload?.filter(item => item.type === "duties");
const totalDutiesScore =dutiesItems?.length? dutiesItems?.reduce((acc, curr) => acc+ Number(curr.score), 0):0;
const defaultAvgScore =totalDutiesScore===0?0: Number(totalDutiesScore) / Number(dutiesItems?.length);
return {...state,defaultAvgScore :Number(defaultAvgScore).toFixed(1)}
}
},
calculateInitiativeScore:(state,action)=>{
if(action.payload===undefined){
return {...state,initiativeAvgScore:0}
}
else{
const initiatiesItems = action.payload?.filter(item => item.type === "initiative");
const totalInitiateScore =initiatiesItems?.length? (initiatiesItems?.reduce((acc, curr) => acc+ Number(curr.score), 0)):0;
const initialAvgScore =totalInitiateScore===0?0: Number(totalInitiateScore) / Number(initiatiesItems?.length) ;
return {...state,initiativeAvgScore :Number(initialAvgScore).toFixed(1)}
}
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(fetchReporteeActivities.pending, (state) => { builder.addCase(fetchReporteeActivities.pending, (state) => {
return {...state,loading :true,error :"loading"} return {...state,loading :true,error :"loading"}
}); });
builder.addCase(fetchReporteeActivities.fulfilled, (state, action) => { builder.addCase(fetchReporteeActivities.fulfilled, (state, action) => {
return {...state,loading :false,error :"",reports:action.payload?.activities} const {data, type} = action.payload;
return {...state,loading :false,error :"", [`${type[0]}Reports`]: data}
}); });
builder.addCase(fetchReporteeActivities.rejected, (state, action) => { builder.addCase(fetchReporteeActivities.rejected, (state, action) => {
return {...state,loading :false,error :action.error || "Something went wrong!",reports:null} return {...state,loading :false,error :action.error || "Something went wrong!",reports:null}
}); });
// getActivities Api
builder.addCase(fetchActivitiesAvg.pending, (state) => {
return {...state,loading :true,error :"loading"}
});
builder.addCase(fetchActivitiesAvg.fulfilled, (state, action) => {
const avgScores = action.payload;
const dutiesAvg = avgScores.find(({type}) => type === "duties")
const initiatieAvg = avgScores.find(({type}) => type === "initiative")
return {...state,loading :false,error :"", defaultAvgScore: dutiesAvg?.avgScore.toFixed(1) || 0, initiativeAvgScore: initiatieAvg?.avgScore.toFixed(1) || 0}
});
builder.addCase(fetchActivitiesAvg.rejected, (state, action) => {
return {...state,loading :false,error :action.error || "Something went wrong!",reports:null}
});
}, },
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment