From 5cde0adffed68554774142c182bb4d07980f0f54 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:24:06 +0530 Subject: [PATCH 01/16] configured employeeAttendance slice in store --- src/store/store.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/store/store.jsx b/src/store/store.jsx index 580407c3..e1d9c03a 100644 --- a/src/store/store.jsx +++ b/src/store/store.jsx @@ -3,6 +3,7 @@ import apiCacheReducer from "../slices/apiCacheSlice"; import globalVariablesReducer from "../slices/globalVariablesSlice"; import localVariableRducer from "../slices/localVariablesSlice" import attendanceReducer from "../slices/apiSlice/attedanceLogsSlice" +import employeeAttendanceReducer from "../slices/apiSlice/employeeAttendanceSlice" export const store = configureStore({ reducer: { @@ -10,5 +11,6 @@ export const store = configureStore({ globalVariables: globalVariablesReducer, localVariables:localVariableRducer, attendanceLogs: attendanceReducer, + employeeAttendance: employeeAttendanceReducer, }, }); From f1e3ce45044454c8cb35df2117496e4eb0294305 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:24:39 +0530 Subject: [PATCH 02/16] created new slice for employeeAttendance --- .../apiSlice/employeeAttendanceSlice.js | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/slices/apiSlice/employeeAttendanceSlice.js diff --git a/src/slices/apiSlice/employeeAttendanceSlice.js b/src/slices/apiSlice/employeeAttendanceSlice.js new file mode 100644 index 00000000..e76c4989 --- /dev/null +++ b/src/slices/apiSlice/employeeAttendanceSlice.js @@ -0,0 +1,57 @@ +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; +import AttendanceRepository from '../../repositories/AttendanceRepository'; +import { markAttendance } from './attedanceLogsSlice'; + +export const fetchEmployeeAttendanceData = createAsyncThunk( + 'employeeAttendance/fetchEmployeeAttendanceData', + async ( {employeeId, fromDate, toDate}, thunkAPI ) => + { + try { + const response = await AttendanceRepository.getAttendanceByEmployee( employeeId, fromDate, toDate ); + console.log(response) + // return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0); + return response.data + } catch (error) { + return thunkAPI.rejectWithValue(error.message); + } + } +); + + +const employeeAttendancesSlice = createSlice({ + name: 'employeeAttendance', // Updated slice name + initialState: { + data: [], + loading: false, + error: null, + }, + reducers: { + setEmployeeAttendanceData: (state, action) => { + state.data = action.payload; + }, + }, + extraReducers: (builder) => { + builder + // Fetch attendance data + .addCase(fetchEmployeeAttendanceData.pending, (state) => { + state.loading = true; + }) + .addCase(fetchEmployeeAttendanceData.fulfilled, (state, action) => { + state.loading = false; + state.data = action.payload; + }) + + + .addCase(fetchEmployeeAttendanceData.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }) + + + + + }, + }); + + export const { setEmployeeAttendanceData } = employeeAttendancesSlice.actions; + export default employeeAttendancesSlice.reducer; \ No newline at end of file From c00b33926a7c522003414b3b682ce2793cf75139 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:26:14 +0530 Subject: [PATCH 03/16] created new repo for attendanceByEmployee --- src/repositories/AttendanceRepository.jsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/repositories/AttendanceRepository.jsx b/src/repositories/AttendanceRepository.jsx index 22725fba..679001f8 100644 --- a/src/repositories/AttendanceRepository.jsx +++ b/src/repositories/AttendanceRepository.jsx @@ -19,7 +19,21 @@ const AttendanceRepository = { }, getAttendanceLogs: ( id ) => api.get( `api/attendance/log/attendance/${ id }` ), - getRegularizeList: (id)=> api.get(`api/attendance/regularize?projectId=${id}`) + getRegularizeList: ( id ) => api.get( `api/attendance/regularize?projectId=${ id }` ), + + getAttendanceByEmployee: ( employeeId, fromDate, toDate ) => + { + + let url = `api/Attendance/log/employee/${ employeeId }?` + if (fromDate) { + url += `&dateFrom=${fromDate}`; + } + + if (toDate) { + url += `&dateTo=${toDate}`; + } + return api.get(url) + }, } From 29e3f8da8c47216e1d06aa1b00de54e8a95c8349 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:35:22 +0530 Subject: [PATCH 04/16] removed console --- src/slices/apiSlice/employeeAttendanceSlice.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slices/apiSlice/employeeAttendanceSlice.js b/src/slices/apiSlice/employeeAttendanceSlice.js index e76c4989..290883c7 100644 --- a/src/slices/apiSlice/employeeAttendanceSlice.js +++ b/src/slices/apiSlice/employeeAttendanceSlice.js @@ -8,7 +8,6 @@ export const fetchEmployeeAttendanceData = createAsyncThunk( { try { const response = await AttendanceRepository.getAttendanceByEmployee( employeeId, fromDate, toDate ); - console.log(response) // return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0); return response.data } catch (error) { From 09cdd675e39c5984aa0206806c43a5f36b3c779f Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:36:09 +0530 Subject: [PATCH 05/16] addded current employee log --- src/pages/employee/EmployeeProfile.jsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/employee/EmployeeProfile.jsx b/src/pages/employee/EmployeeProfile.jsx index 9e5debc7..8bf3e7f5 100644 --- a/src/pages/employee/EmployeeProfile.jsx +++ b/src/pages/employee/EmployeeProfile.jsx @@ -11,6 +11,7 @@ import EmployeeRepository from "../../repositories/EmployeeRepository"; import { ComingSoonPage } from "../Misc/ComingSoonPage"; import { useNavigate } from "react-router-dom"; import Avatar from "../../components/common/Avatar"; +import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords"; const EmployeeProfile = () => { const projectID = useSelector((store)=>store.localVariables.projectId) @@ -54,17 +55,18 @@ const EmployeeProfile = () => { const renderContent = () => { if (loading) return
Loading
; switch (activePill) { - case "account": { - return ( - <> - - - ); - } case "attendance": { return ( <> - + + + + ); + } + case "dcoument": { + return ( + <> + ); break; From cbe439e63208083fbc0ff0c5b4e62a7c954c1aab Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:36:46 +0530 Subject: [PATCH 06/16] added employee attendance tab with date filtering and pagination in profile page --- .../employee/AttendancesEmployeeRecords.jsx | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/pages/employee/AttendancesEmployeeRecords.jsx diff --git a/src/pages/employee/AttendancesEmployeeRecords.jsx b/src/pages/employee/AttendancesEmployeeRecords.jsx new file mode 100644 index 00000000..ee363d11 --- /dev/null +++ b/src/pages/employee/AttendancesEmployeeRecords.jsx @@ -0,0 +1,267 @@ +import React, { useState, useEffect } from "react"; +import moment from "moment"; +import { useAttendanceByEmployee } from "../../hooks/useAttendance"; +import DateRangePicker from "../../components/common/DateRangePicker"; +import { useDispatch, useSelector } from "react-redux"; +import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice"; +import usePagination from "../../hooks/usePagination"; +import Avatar from "../../components/common/Avatar"; +import { convertShortTime } from "../../utils/dateUtils"; +import RenderAttendanceStatus from "../../components/Activities/RenderAttendanceStatus"; +import AttendLogs from "../../components/Activities/AttendLogs"; + +const AttendancesEmployeeRecords = ({ employee }) => { + const [attendances, setAttendnaces] = useState([]); + const [selectedDate, setSelectedDate] = useState(""); + const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); + const [isModalOpen, setIsModalOpen] = useState(false); + const [attendanceId, setAttendanecId] = useState(); + const dispatch = useDispatch(); + const { data, loading, error } = useSelector( + (store) => store.employeeAttendance + ); + + const [isRefreshing, setIsRefreshing] = useState(true); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const isSameDay = (dateStr) => { + if (!dateStr) return false; + const d = new Date(dateStr); + d.setHours(0, 0, 0, 0); + return d.getTime() === today.getTime(); + }; + + const isBeforeToday = (dateStr) => { + if (!dateStr) return false; + const d = new Date(dateStr); + d.setHours(0, 0, 0, 0); + return d.getTime() < today.getTime(); + }; + + const sortByName = (a, b) => { + const nameA = a.firstName.toLowerCase() + a.lastName.toLowerCase(); + const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase(); + return nameA.localeCompare(nameB); + }; + + const group1 = data + .filter((d) => d.activity === 1 && isSameDay(d.checkInTime)) + .sort(sortByName); + const group2 = data + .filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)) + .sort(sortByName); + const group3 = data + .filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)) + .sort(sortByName); + const group4 = data + .filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime)) + .sort(sortByName); + const group5 = data.filter((d) => d.activity === 5).sort(sortByName); + + const sortedFinalList = [ + ...group1, + ...group2, + ...group3, + ...group4, + ...group5, + ]; + + const currentDate = new Date().toLocaleDateString("en-CA"); + const { currentPage, totalPages, currentItems, paginate } = usePagination( + sortedFinalList, + 5 + ); + + useEffect(() => { + const { startDate, endDate } = dateRange; + if (startDate && endDate) { + dispatch( + fetchEmployeeAttendanceData({ + employeeId: employee, + fromDate: startDate, + toDate: endDate, + }) + ); + } + }, [dateRange, employee]); + + const openModal = (id) => { + setAttendanecId(id); + setIsModalOpen(true); + }; + const closeModal = () => setIsModalOpen(false); + + return ( + <> +
+ {" "} +
+
+
+ + + +
+
+
+
+
+
+
+ +
+
+ setIsRefreshing(!isRefreshing)} + /> +
+
+
+ {data && data.length > 0 ? ( + + + + + + + + + + + + {loading && } + {error && } + {data && data.length === 0 && ( + + + + )} + + {currentItems?.map((attendance, index) => ( + + + + + + + + + ))} + +
+ Name + Date + {" "} + Check-In + + {" "} + Check-Out + Action
Loading...{error}
No Data Found
+ + + {" "} + {moment(attendance.checkInTime).format("DD-MMM-YYYY")} + {convertShortTime(attendance.checkInTime)} + {attendance.checkOutTime + ? convertShortTime(attendance.checkOutTime) + : "--"} + + +
+ ) : ( + No employee logs + )} +
+ {!loading && ( + + )} +
+ + ); +}; + +export default AttendancesEmployeeRecords; From 0c0476a86f134ca6fbbf07fad46692d763b8b448 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 15:38:27 +0530 Subject: [PATCH 07/16] moved attendance tap at first --- src/components/Employee/EmployeeNav.jsx | 30 +++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/Employee/EmployeeNav.jsx b/src/components/Employee/EmployeeNav.jsx index 9e239682..f9c12961 100644 --- a/src/components/Employee/EmployeeNav.jsx +++ b/src/components/Employee/EmployeeNav.jsx @@ -5,6 +5,21 @@ const EmployeeNav = ({ onPillClick, activePill }) => {
- {data && data.length > 0 ? ( + {(!loading && data.length === 0) && + No employee logs + } + {error &&
{error }
} + {(loading && !data ) &&
Loading...
} + {(data && data.length > 0 ) && ( @@ -159,15 +165,7 @@ const AttendancesEmployeeRecords = ({ employee }) => { - {loading && } - {error && } - {data && data.length === 0 && ( - - - - )} - - {currentItems?.map((attendance, index) => ( + {currentItems?.map( ( attendance, index ) => ( - + @@ -202,20 +200,18 @@ const AttendancesEmployeeRecords = ({ employee }) => { tabIndex="0" aria-controls="DataTables_Table_0" data-bs-toggle="modal" - onClick={() => openModal(attendance.id)} + onClick={() => openModal( attendance.id )} > View - ))} + ) )}
Loading...{error}
No Data Found
@@ -186,12 +184,12 @@ const AttendancesEmployeeRecords = ({ employee }) => {
{" "} - {moment(attendance.checkInTime).format("DD-MMM-YYYY")} + {moment( attendance.checkInTime ).format( "DD-MMM-YYYY" )} {convertShortTime(attendance.checkInTime)}{convertShortTime( attendance.checkInTime )} {attendance.checkOutTime - ? convertShortTime(attendance.checkOutTime) + ? convertShortTime( attendance.checkOutTime ) : "--"}
- ) : ( - No employee logs - )} + ) }
- {!loading && ( + {(!loading && currentItems < 5) && (
- {(!loading && currentItems < 5) && ( + {(!loading && data.length > 5) && ( - )} + )} ); }; From d48c6fd7e36c8f291cc30de7cbae373c5ec86908 Mon Sep 17 00:00:00 2001 From: Pramod Mahajan Date: Tue, 6 May 2025 23:13:55 +0530 Subject: [PATCH 16/16] added activity icons, location coulms. if click on location it move on google map --- src/components/Activities/AttendLogs.jsx | 167 ++++++++++++++++------- 1 file changed, 121 insertions(+), 46 deletions(-) diff --git a/src/components/Activities/AttendLogs.jsx b/src/components/Activities/AttendLogs.jsx index 85424943..ec64f5d1 100644 --- a/src/components/Activities/AttendLogs.jsx +++ b/src/components/Activities/AttendLogs.jsx @@ -1,80 +1,155 @@ -import React, { useEffect, useState } from 'react' -import { useEmployeeAttendacesLog } from '../../hooks/useAttendance'; -import { convertShortTime } from '../../utils/dateUtils'; - +import React, { useEffect, useState } from "react"; +import { useEmployeeAttendacesLog } from "../../hooks/useAttendance"; +import { convertShortTime } from "../../utils/dateUtils"; +import { useNavigate } from "react-router-dom"; const AttendLogs = ({ Id }) => { - - const {logs, loading} = useEmployeeAttendacesLog( Id ) + const { logs, loading } = useEmployeeAttendacesLog(Id); + const navigate = useNavigate(); - const whichActivityPerform = ( actvity ) => - { + const whichActivityPerform = (actvity) => { switch (actvity) { case 1: - return "IN" + return ( + + ); break; - case 2: - return "Requested" + case 2: + return ( + + ); break; - case 3: - return "Deleted" + case 3: + return ( + + ); break; - case 4: - return "OUT" + case 4: + return ( + + ); + break; - case 5: - return "Regularized" - break; - + case 5: + return ( + + ); + break; + default: break; } - } - + }; + const LocationLink = (lat, lng) => { + const url = `https://www.google.com/maps?q=${lat},${lng}`; + window.open(url, "_blank"); // Open in new tab + }; + useEffect(() => { + const tooltipTriggerList = Array.from( + document.querySelectorAll('[data-bs-toggle="tooltip"]') + ); + tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); + }, []); return (
- {loading &&

Loading..

} - { logs && logs.length > 0 && ( - <> -
-
-

Date : {logs[0].activityTime.slice(0,10)}

-
+
+ {logs && !loading && ( +

+ Attendance for{" "} + {logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}{" "} + on {logs[0]?.activityTime.slice(0, 10)}{" "} +

+ )}
+ {loading &&

Loading..

} + {logs && logs.length > 0 && ( + <> +
+
+
- - - - - - - - - - +
TimeActivityDateDescription
+ + + + + + + + + + {logs .slice() .sort((a, b) => b.id - a.id) .map((log, index) => ( - - + + + ))} -
DateTimeActivityLocationDescription
{convertShortTime( log.activityTime )}{whichActivityPerform(log.activity)} {log.activityTime.slice(0, 10)}{convertShortTime(log.activityTime)}{whichActivityPerform(log.activity)} + {log?.latitude != 0 ? ( + + LocationLink(log?.latitude, log?.longitude) + } + > + ) : ( + "--" + )} + {log?.comment}
+ - ) - } - + )}
); }; export default AttendLogs; - -