From 41645d0f8284530e5f12bcfae6fd07ac655352ee Mon Sep 17 00:00:00 2001 From: RishiChaubey31 Date: Wed, 29 Oct 2025 10:18:30 +0530 Subject: [PATCH 1/4] initial commit --- src/app/services/api.ts | 1 + src/app/services/logsApi.ts | 42 +++++++ src/components/Calendar/UserSearchField.tsx | 40 ++++++- src/constants/url.ts | 1 + src/pages/calendar/index.tsx | 115 +++++++++++++++++--- src/styles/calendar.scss | 10 ++ src/utils/time.ts | 22 ++++ src/utils/userStatusCalendar.ts | 93 ++++++++++++---- 8 files changed, 284 insertions(+), 40 deletions(-) create mode 100644 src/app/services/logsApi.ts diff --git a/src/app/services/api.ts b/src/app/services/api.ts index b8cb919b6..6b55d3c6f 100644 --- a/src/app/services/api.ts +++ b/src/app/services/api.ts @@ -28,6 +28,7 @@ export const api = createApi({ 'User_Standup', 'TASK_REQUEST', 'Extension_Requests', + 'Logs', ], /** * This api has endpoints injected in adjacent files, diff --git a/src/app/services/logsApi.ts b/src/app/services/logsApi.ts new file mode 100644 index 000000000..c0a653f6c --- /dev/null +++ b/src/app/services/logsApi.ts @@ -0,0 +1,42 @@ +import { api } from './api'; + +export interface LogEntry { + user?: string; + requestId: string; + from: number; + until: number; + type: string; + timestamp: number; + message?: string; +} + +export interface LogsResponse { + message: string; + data: LogEntry[]; + next: string | null; + prev: string | null; +} + +export interface LogsQueryArgs { + username: string; + dev?: boolean; + format?: string; + type?: string; +} + +export const logsApi = api.injectEndpoints({ + endpoints: (build) => ({ + getLogsByUsername: build.query({ + query: ({ + username, + dev = true, + format = 'feed', + type = 'REQUEST_CREATED', + }) => + `/logs?dev=${dev}&format=${format}&type=${type}&username=${username}`, + providesTags: ['Logs'], + }), + }), +}); + +export const { useGetLogsByUsernameQuery } = logsApi; diff --git a/src/components/Calendar/UserSearchField.tsx b/src/components/Calendar/UserSearchField.tsx index 607abe100..aeb51e5c6 100644 --- a/src/components/Calendar/UserSearchField.tsx +++ b/src/components/Calendar/UserSearchField.tsx @@ -1,22 +1,36 @@ import { useState, useEffect, ChangeEvent, useRef } from 'react'; import classNames from './UserSearchField.module.scss'; import { useGetAllUsersQuery } from '@/app/services/usersApi'; +import { useGetLogsByUsernameQuery } from '@/app/services/logsApi'; import { logs } from '@/constants/calendar'; import { userDataType } from '@/interfaces/user.type'; import { useOutsideAlerter } from '@/hooks/useOutsideAlerter'; +import { LogEntry } from '@/app/services/logsApi'; type SearchFieldProps = { - onSearchTextSubmitted: (user: userDataType | undefined, data: any) => void; + onSearchTextSubmitted: ( + user: userDataType | undefined, + data: any, + oooLogsData?: LogEntry[] + ) => void; loading: boolean; + dev?: boolean; }; -const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { +const SearchField = ({ + onSearchTextSubmitted, + loading, + dev = false, +}: SearchFieldProps) => { const handleOutsideClick = () => { setDisplayList([]); }; const suggestionInputRef = useRef(null); useOutsideAlerter(suggestionInputRef, handleOutsideClick); const [searchText, setSearchText] = useState(''); + const [selectedUser, setSelectedUser] = useState(null); + const lastProcessedUsername = useRef(null); + const onSearchTextChanged = (e: ChangeEvent) => { setSearchText(e.target.value); filterUser(e.target.value); @@ -28,6 +42,9 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { const user = usersList.find( (user: userDataType) => user.username === searchText ); + setSelectedUser(user || null); + // Reset the tracking ref when submitting a new user + lastProcessedUsername.current = null; onSearchTextSubmitted(user, data); }; @@ -36,6 +53,12 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { const [displayList, setDisplayList] = useState([]); const [data, setData] = useState([]); + // Fetch OOO logs data when dev=true and user is selected + const { data: logsData } = useGetLogsByUsernameQuery( + { username: selectedUser?.username || '' }, + { skip: !dev || !selectedUser?.username } + ); + useEffect(() => { if (userData?.users) { const users: userDataType[] = userData.users; @@ -54,6 +77,19 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { } }, [isLoading, userData]); + // Update parent component when logs data changes - only once per user + useEffect(() => { + if ( + dev && + logsData?.data && + selectedUser && + lastProcessedUsername.current !== selectedUser.username + ) { + lastProcessedUsername.current = selectedUser.username || null; + onSearchTextSubmitted(selectedUser, data, logsData.data); + } + }, [dev, logsData, selectedUser]); + const isValidUsername = () => { const usernames = usersList.map((user: userDataType) => user.username); if (usernames.includes(searchText)) { diff --git a/src/constants/url.ts b/src/constants/url.ts index 901cef8f3..59cdb882e 100644 --- a/src/constants/url.ts +++ b/src/constants/url.ts @@ -34,3 +34,4 @@ export const DASHBOARD_URL = 'https://dashboard.realdevsquad.com'; export const USER_MANAGEMENT_URL = `${DASHBOARD_URL}/users/details/`; export const TASK_REQUESTS_DETAILS_URL = `${DASHBOARD_URL}/task-requests/details/`; export const TASK_EXTENSION_REQUEST_URL = `${DASHBOARD_URL}/extension-requests/`; +export const OOO_REQUEST_DETAILS_URL = `${DASHBOARD_URL}/requests/`; diff --git a/src/pages/calendar/index.tsx b/src/pages/calendar/index.tsx index 1136757d9..98c1442b0 100644 --- a/src/pages/calendar/index.tsx +++ b/src/pages/calendar/index.tsx @@ -1,28 +1,72 @@ import { FC, useState } from 'react'; +import { useRouter } from 'next/router'; import Head from '@/components/head'; import Layout from '@/components/Layout'; import Calendar from 'react-calendar'; import 'react-calendar/dist/Calendar.css'; import { SearchField } from '@/components/Calendar/UserSearchField'; -import { processData } from '@/utils/userStatusCalendar'; +import { processData, OOOEntry } from '@/utils/userStatusCalendar'; +import { formatTimestampToDate } from '@/utils/time'; +import { OOO_REQUEST_DETAILS_URL } from '@/constants/url'; import { MONTHS } from '@/constants/calendar'; +import { userDataType } from '@/interfaces/user.type'; const UserStatusCalendar: FC = () => { + const router = useRouter(); + const { dev } = router.query; + const isDevMode = dev === 'true'; + const [selectedDate, onDateChange] = useState(new Date()); - const [selectedUser, setSelectedUser]: any = useState(null); - const [processedData, setProcessedData] = useState( - processData(selectedUser ? selectedUser.id : null, []) - ); + const [selectedUser, setSelectedUser] = useState(null); + const [processedData, setProcessedData] = useState< + [ + Record, + Record, + Record + ] + >([{}, {}, {}] as [ + Record, + Record, + Record + ]); - const [message, setMessage]: any = useState(null); - const [loading, setLoading]: any = useState(false); + const [message, setMessage] = useState(null); - const setTileClassName = ({ activeStartDate, date, view }: any) => { + const setTileClassName = ({ date }: { date: Date }) => { if (date.getDay() === 0) return 'sunday'; + + // Check for OOO entries first (new API data) + if (processedData[2] && processedData[2][date.getTime()]) { + return 'OOO'; + } + + // Check for existing status (mock data) return processedData[0] ? processedData[0][date.getTime()] : null; }; - const handleDayClick = (value: Date, event: any) => { + const formatOOOMessage = (oooEntries: OOOEntry[]): string => { + return oooEntries + .map((entry) => { + const fromDate = formatTimestampToDate(entry.from); + const untilDate = formatTimestampToDate(entry.until); + const requestLink = `${OOO_REQUEST_DETAILS_URL}`; + + let messageText = `From: ${fromDate}\nUntil: ${untilDate}\nRequest ID: `; + messageText += `${entry.requestId}`; + + if (entry.message) { + messageText += `\nMessage: ${entry.message}`; + } + + return messageText; + }) + .join('\n\n---\n\n'); + }; + + const handleDayClick = ( + value: Date, + event: React.MouseEvent + ) => { if (value.getDay() === 0) { setMessage( `${value.getDate()}-${ @@ -31,9 +75,30 @@ const UserStatusCalendar: FC = () => { ); return; } + + // Check for OOO entries first (new feature) + if (processedData[2] && processedData[2][value.getTime()]) { + const oooEntries = processedData[2][value.getTime()]; + const formattedMessage = formatOOOMessage(oooEntries); + setMessage( +
+
{`${ + selectedUser?.username + } is OOO on ${value.getDate()}-${ + MONTHS[value.getMonth()] + }-${value.getFullYear()}`}
+
+
+ ); + return; + } + if (event.currentTarget.classList.contains('OOO')) { setMessage( - `${selectedUser.username} is OOO on ${value.getDate()}-${ + `${selectedUser?.username} is OOO on ${value.getDate()}-${ MONTHS[value.getMonth()] }-${value.getFullYear()}` ); @@ -41,7 +106,7 @@ const UserStatusCalendar: FC = () => { } if (event.currentTarget.classList.contains('IDLE')) { setMessage( - `${selectedUser.username} is IDLE on ${value.getDate()}-${ + `${selectedUser?.username} is IDLE on ${value.getDate()}-${ MONTHS[value.getMonth()] }-${value.getFullYear()}` ); @@ -49,7 +114,7 @@ const UserStatusCalendar: FC = () => { } if (processedData[1] && processedData[1][value.getTime()]) { setMessage( - `${selectedUser.username} is ACTIVE on ${value.getDate()}-${ + `${selectedUser?.username} is ACTIVE on ${value.getDate()}-${ MONTHS[value.getMonth()] }-${value.getFullYear()} having task with title - ${ processedData[1][value.getTime()] @@ -60,7 +125,7 @@ const UserStatusCalendar: FC = () => { setMessage( `No user status found for ${ - selectedUser.username + selectedUser?.username } on ${value.getDate()}-${ MONTHS[value.getMonth()] }-${value.getFullYear()}!` @@ -73,19 +138,33 @@ const UserStatusCalendar: FC = () => {
{ - setSelectedUser(user); + onSearchTextSubmitted={(user, data, oooLogsData) => { + setSelectedUser(user || null); + const processed = processData( + user ? user.id : null, + data, + oooLogsData + ); setProcessedData( - processData(user ? user.id : null, data) + processed as [ + Record, + Record, + Record + ] ); setMessage(null); }} - loading={loading} + loading={false} + dev={isDevMode} /> {selectedUser && (
{ + if (value instanceof Date) { + onDateChange(value); + } + }} className="calendar-div" value={selectedDate} onClickDay={handleDayClick} diff --git a/src/styles/calendar.scss b/src/styles/calendar.scss index 4bbe5b3da..f1de1b870 100644 --- a/src/styles/calendar.scss +++ b/src/styles/calendar.scss @@ -42,6 +42,16 @@ border-radius: 10px; text-align: center; position: relative; + white-space: pre-line; +} + +.messageDiv a { + color: #2563eb; + text-decoration: underline; +} + +.messageDiv a:hover { + color: #1d4ed8; } .messageDiv:before { diff --git a/src/utils/time.ts b/src/utils/time.ts index 0137c8ff7..b3feec5ab 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -23,3 +23,25 @@ export const getDateRelativeToToday = ( ); } }; + +export const formatTimestampToDate = (timestamp: number): string => { + const date = new Date(timestamp); + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + return `${ + months[date.getMonth()] + } ${date.getDate()}, ${date.getFullYear()}`; +}; diff --git a/src/utils/userStatusCalendar.ts b/src/utils/userStatusCalendar.ts index aa1787a55..76439223f 100644 --- a/src/utils/userStatusCalendar.ts +++ b/src/utils/userStatusCalendar.ts @@ -1,3 +1,5 @@ +import { LogEntry } from '@/app/services/logsApi'; + interface LOG_TYPE { userId: string; data: []; @@ -10,6 +12,16 @@ interface LOG_DATA { taskTitle: string; } +export interface OOOEntry { + from: number; + until: number; + requestId: string; + message?: string; + timestamp: number; + user?: string; + type: string; +} + export const getStartOfDay = (date: Date): Date => { if (date instanceof Date && !isNaN(date.getTime())) return new Date(date.getFullYear(), date.getMonth(), date.getDate()); @@ -38,34 +50,75 @@ export const getDatesInRange = (startDate: Date, endDate: Date) => { return dates; }; +export const processOOOLogsData = ( + logsData: LogEntry[] +): [Record, Record] => { + const dictWithOOOEntries: Record = {}; + const dictWithTask: Record = {}; + + logsData.forEach((logEntry: LogEntry) => { + const dates = getDatesInRange( + new Date(logEntry.from), + new Date(logEntry.until) + ); + + dates.forEach((dateTimestamp) => { + if (!dictWithOOOEntries[dateTimestamp]) { + dictWithOOOEntries[dateTimestamp] = []; + } + dictWithOOOEntries[dateTimestamp].push(logEntry); + }); + }); + + return [dictWithOOOEntries, dictWithTask]; +}; + export const processData = ( itemId: string | null, - data: [] -): [object, object] => { + data: [], + oooLogsData?: LogEntry[] +): [object, object, Record] => { if (!itemId) { - return [{}, {}]; + return [{}, {}, {}]; } else { const log: any = data.find((log: LOG_TYPE) => { return log.userId === itemId; }); - if (!log || log.data?.length == 0) return [{}, {}]; + const dictWithStatus: Record = {}; const dictWithTask: Record = {}; - log.data.forEach((logData: LOG_DATA) => { - const dates = getDatesInRange( - new Date(logData.startTime), - new Date(logData.endTime) - ); - if (logData.status === 'ACTIVE') { - dates.forEach((dateTimestamp) => { - dictWithTask[dateTimestamp] = logData.taskTitle; - }); - } else { - dates.forEach((dateTimestamp) => { - dictWithStatus[dateTimestamp] = logData.status; - }); - } - }); - return [dictWithStatus, dictWithTask]; + const dictWithOOOEntries: Record = {}; + + // Process mock data if available + if (log && log.data?.length > 0) { + log.data.forEach((logData: LOG_DATA) => { + const dates = getDatesInRange( + new Date(logData.startTime), + new Date(logData.endTime) + ); + if (logData.status === 'ACTIVE') { + dates.forEach((dateTimestamp) => { + dictWithTask[dateTimestamp] = logData.taskTitle; + }); + } else { + dates.forEach((dateTimestamp) => { + dictWithStatus[dateTimestamp] = logData.status; + }); + } + }); + } + + // Process OOO logs data if available + if (oooLogsData && oooLogsData.length > 0) { + const [oooEntries] = processOOOLogsData(oooLogsData); + Object.assign(dictWithOOOEntries, oooEntries); + + // Also add "OOO" status to dictWithStatus for proper tile styling + Object.keys(oooEntries).forEach((dateTimestamp) => { + dictWithStatus[parseInt(dateTimestamp)] = 'OOO'; + }); + } + + return [dictWithStatus, dictWithTask, dictWithOOOEntries]; } }; From 2163ed1dbc1168e19bf1c54a858fb25d0d96d66e Mon Sep 17 00:00:00 2001 From: RishiChaubey31 Date: Wed, 29 Oct 2025 10:40:26 +0530 Subject: [PATCH 2/4] fixed tests and removed comments --- .../ExtensionRequest/ExtensionStatusModal.test.tsx | 2 ++ __tests__/Unit/Components/Tasks/Card.test.tsx | 7 ++++++- src/app/services/logsApi.ts | 2 +- src/components/Calendar/UserSearchField.tsx | 3 --- src/utils/userStatusCalendar.ts | 3 --- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/__tests__/Unit/Components/ExtensionRequest/ExtensionStatusModal.test.tsx b/__tests__/Unit/Components/ExtensionRequest/ExtensionStatusModal.test.tsx index 4996dabf2..e3eee08f0 100644 --- a/__tests__/Unit/Components/ExtensionRequest/ExtensionStatusModal.test.tsx +++ b/__tests__/Unit/Components/ExtensionRequest/ExtensionStatusModal.test.tsx @@ -120,9 +120,11 @@ describe('ExtensionStatusModal Component', () => { }); test('should test formatToRelativeTime function', () => { + jest.useFakeTimers().setSystemTime(new Date('2025-01-01T00:00:00Z')); const timestamp = 1640995200; const result = formatToRelativeTime(timestamp); expect(result).toBe('3 years ago'); + jest.useRealTimers(); }); test('should open extension request form when request extension button is clicked', () => { diff --git a/__tests__/Unit/Components/Tasks/Card.test.tsx b/__tests__/Unit/Components/Tasks/Card.test.tsx index 025f1cbfa..8cbec970f 100644 --- a/__tests__/Unit/Components/Tasks/Card.test.tsx +++ b/__tests__/Unit/Components/Tasks/Card.test.tsx @@ -23,6 +23,7 @@ import { NEEDS_REVIEW, VERIFIED, } from '@/constants/task-status'; +import moment from 'moment'; const DEFAULT_PROPS = { content: { @@ -537,6 +538,9 @@ describe('Task card', () => { }); it('renders "Started" with a specific date if status is not AVAILABLE', () => { + const originalFromNow = moment.prototype.fromNow; + moment.prototype.fromNow = jest.fn(() => '4 years ago'); + const { getByTestId } = renderWithRouter( { {} ); const spanElement = screen.getByTestId('started-on'); - expect(spanElement).toHaveTextContent('Started 4 years ago'); // Mocked date from moment + expect(spanElement).toHaveTextContent('Started 4 years ago'); + moment.prototype.fromNow = originalFromNow; }); it('Should show the status of the task', () => { renderWithRouter( diff --git a/src/app/services/logsApi.ts b/src/app/services/logsApi.ts index c0a653f6c..ff9ecf7ef 100644 --- a/src/app/services/logsApi.ts +++ b/src/app/services/logsApi.ts @@ -29,7 +29,7 @@ export const logsApi = api.injectEndpoints({ getLogsByUsername: build.query({ query: ({ username, - dev = true, + dev = false, format = 'feed', type = 'REQUEST_CREATED', }) => diff --git a/src/components/Calendar/UserSearchField.tsx b/src/components/Calendar/UserSearchField.tsx index aeb51e5c6..1698bdc7c 100644 --- a/src/components/Calendar/UserSearchField.tsx +++ b/src/components/Calendar/UserSearchField.tsx @@ -43,7 +43,6 @@ const SearchField = ({ (user: userDataType) => user.username === searchText ); setSelectedUser(user || null); - // Reset the tracking ref when submitting a new user lastProcessedUsername.current = null; onSearchTextSubmitted(user, data); }; @@ -53,7 +52,6 @@ const SearchField = ({ const [displayList, setDisplayList] = useState([]); const [data, setData] = useState([]); - // Fetch OOO logs data when dev=true and user is selected const { data: logsData } = useGetLogsByUsernameQuery( { username: selectedUser?.username || '' }, { skip: !dev || !selectedUser?.username } @@ -77,7 +75,6 @@ const SearchField = ({ } }, [isLoading, userData]); - // Update parent component when logs data changes - only once per user useEffect(() => { if ( dev && diff --git a/src/utils/userStatusCalendar.ts b/src/utils/userStatusCalendar.ts index 76439223f..211b07a9d 100644 --- a/src/utils/userStatusCalendar.ts +++ b/src/utils/userStatusCalendar.ts @@ -89,7 +89,6 @@ export const processData = ( const dictWithTask: Record = {}; const dictWithOOOEntries: Record = {}; - // Process mock data if available if (log && log.data?.length > 0) { log.data.forEach((logData: LOG_DATA) => { const dates = getDatesInRange( @@ -108,12 +107,10 @@ export const processData = ( }); } - // Process OOO logs data if available if (oooLogsData && oooLogsData.length > 0) { const [oooEntries] = processOOOLogsData(oooLogsData); Object.assign(dictWithOOOEntries, oooEntries); - // Also add "OOO" status to dictWithStatus for proper tile styling Object.keys(oooEntries).forEach((dateTimestamp) => { dictWithStatus[parseInt(dateTimestamp)] = 'OOO'; }); From f87e3c1d63e0d0eb1b7f63651486353ef6fba3ae Mon Sep 17 00:00:00 2001 From: RishiChaubey31 Date: Thu, 30 Oct 2025 20:13:37 +0530 Subject: [PATCH 3/4] fixed ai comments --- src/components/Calendar/UserSearchField.tsx | 16 +++++-- src/pages/calendar/index.tsx | 53 +++++++++++++-------- src/utils/time.ts | 32 ++++++------- src/utils/userStatusCalendar.ts | 10 +--- 4 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/components/Calendar/UserSearchField.tsx b/src/components/Calendar/UserSearchField.tsx index 1698bdc7c..d136ef1f5 100644 --- a/src/components/Calendar/UserSearchField.tsx +++ b/src/components/Calendar/UserSearchField.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, ChangeEvent, useRef } from 'react'; +import { useState, useEffect, ChangeEvent, useRef, useMemo } from 'react'; import classNames from './UserSearchField.module.scss'; import { useGetAllUsersQuery } from '@/app/services/usersApi'; import { useGetLogsByUsernameQuery } from '@/app/services/logsApi'; @@ -52,11 +52,17 @@ const SearchField = ({ const [displayList, setDisplayList] = useState([]); const [data, setData] = useState([]); - const { data: logsData } = useGetLogsByUsernameQuery( - { username: selectedUser?.username || '' }, - { skip: !dev || !selectedUser?.username } + const queryParams = useMemo( + () => ({ + username: selectedUser?.username || '', + }), + [selectedUser?.username] ); + const { data: logsData } = useGetLogsByUsernameQuery(queryParams, { + skip: !dev || !selectedUser?.username, + }); + useEffect(() => { if (userData?.users) { const users: userDataType[] = userData.users; @@ -85,7 +91,7 @@ const SearchField = ({ lastProcessedUsername.current = selectedUser.username || null; onSearchTextSubmitted(selectedUser, data, logsData.data); } - }, [dev, logsData, selectedUser]); + }, [dev, logsData, selectedUser, data]); const isValidUsername = () => { const usernames = usersList.map((user: userDataType) => user.username); diff --git a/src/pages/calendar/index.tsx b/src/pages/calendar/index.tsx index 98c1442b0..65a7cfaf3 100644 --- a/src/pages/calendar/index.tsx +++ b/src/pages/calendar/index.tsx @@ -44,23 +44,37 @@ const UserStatusCalendar: FC = () => { return processedData[0] ? processedData[0][date.getTime()] : null; }; - const formatOOOMessage = (oooEntries: OOOEntry[]): string => { - return oooEntries - .map((entry) => { - const fromDate = formatTimestampToDate(entry.from); - const untilDate = formatTimestampToDate(entry.until); - const requestLink = `${OOO_REQUEST_DETAILS_URL}`; - - let messageText = `From: ${fromDate}\nUntil: ${untilDate}\nRequest ID: `; - messageText += `${entry.requestId}`; - - if (entry.message) { - messageText += `\nMessage: ${entry.message}`; - } - - return messageText; - }) - .join('\n\n---\n\n'); + const formatOOOMessage = (oooEntries: OOOEntry[]): JSX.Element => { + return ( + <> + {oooEntries.map((entry, index) => ( +
+ {index > 0 && ( +
+ )} +
From: {formatTimestampToDate(entry.from)}
+
Until: {formatTimestampToDate(entry.until)}
+
+ Request ID:{' '} + + {entry.requestId} + +
+ {entry.message &&
Message: {entry.message}
} +
+ ))} + + ); }; const handleDayClick = ( @@ -87,10 +101,7 @@ const UserStatusCalendar: FC = () => { } is OOO on ${value.getDate()}-${ MONTHS[value.getMonth()] }-${value.getFullYear()}`}
-
+
{formattedMessage}
); return; diff --git a/src/utils/time.ts b/src/utils/time.ts index b3feec5ab..931114c95 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -24,24 +24,24 @@ export const getDateRelativeToToday = ( } }; +const MONTHS = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', +]; + export const formatTimestampToDate = (timestamp: number): string => { const date = new Date(timestamp); - const months = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - return `${ - months[date.getMonth()] + MONTHS[date.getMonth()] } ${date.getDate()}, ${date.getFullYear()}`; }; diff --git a/src/utils/userStatusCalendar.ts b/src/utils/userStatusCalendar.ts index 211b07a9d..1156fe183 100644 --- a/src/utils/userStatusCalendar.ts +++ b/src/utils/userStatusCalendar.ts @@ -12,15 +12,7 @@ interface LOG_DATA { taskTitle: string; } -export interface OOOEntry { - from: number; - until: number; - requestId: string; - message?: string; - timestamp: number; - user?: string; - type: string; -} +export type OOOEntry = LogEntry; export const getStartOfDay = (date: Date): Date => { if (date instanceof Date && !isNaN(date.getTime())) From 9877e9a30b10cb9ca7951ae1c3d6ae3d028a4c90 Mon Sep 17 00:00:00 2001 From: RishiChaubey31 Date: Sat, 1 Nov 2025 22:18:08 +0530 Subject: [PATCH 4/4] fix peer comments --- src/components/Calendar/UserSearchField.tsx | 54 ++++++------- src/pages/calendar/index.tsx | 90 ++++++++++----------- src/utils/userStatusCalendar.ts | 58 ++++++------- 3 files changed, 99 insertions(+), 103 deletions(-) diff --git a/src/components/Calendar/UserSearchField.tsx b/src/components/Calendar/UserSearchField.tsx index d136ef1f5..56f91ffc3 100644 --- a/src/components/Calendar/UserSearchField.tsx +++ b/src/components/Calendar/UserSearchField.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, ChangeEvent, useRef, useMemo } from 'react'; +import { useState, useEffect, ChangeEvent, useRef } from 'react'; import classNames from './UserSearchField.module.scss'; import { useGetAllUsersQuery } from '@/app/services/usersApi'; import { useGetLogsByUsernameQuery } from '@/app/services/logsApi'; @@ -6,11 +6,12 @@ import { logs } from '@/constants/calendar'; import { userDataType } from '@/interfaces/user.type'; import { useOutsideAlerter } from '@/hooks/useOutsideAlerter'; import { LogEntry } from '@/app/services/logsApi'; +import { LOG_DATA } from '@/utils/userStatusCalendar'; type SearchFieldProps = { onSearchTextSubmitted: ( user: userDataType | undefined, - data: any, + data: Array<{ userId: string; data: LOG_DATA[] }>, oooLogsData?: LogEntry[] ) => void; loading: boolean; @@ -50,47 +51,44 @@ const SearchField = ({ const { data: userData, isError, isLoading } = useGetAllUsersQuery(); const [usersList, setUsersList] = useState([]); const [displayList, setDisplayList] = useState([]); - const [data, setData] = useState([]); + const [data, setData] = useState< + Array<{ userId: string; data: LOG_DATA[] }> + >([]); - const queryParams = useMemo( - () => ({ - username: selectedUser?.username || '', - }), - [selectedUser?.username] + const { data: logsData } = useGetLogsByUsernameQuery( + { username: selectedUser?.username || '' }, + { + skip: !dev || !selectedUser?.username, + } ); - const { data: logsData } = useGetLogsByUsernameQuery(queryParams, { - skip: !dev || !selectedUser?.username, - }); - useEffect(() => { if (userData?.users) { const users: userDataType[] = userData.users; const filteredUsers: userDataType[] = users.filter( (user: userDataType) => !user.incompleteUserDetails ); - const logData: any = filteredUsers.map((user: userDataType) => { - const log = logs[Math.floor(Math.random() * 4)]; - return { - data: log, - userId: user.id, - }; - }); + const logData: Array<{ userId: string; data: LOG_DATA[] }> = + filteredUsers.map((user: userDataType) => { + const log = logs[Math.floor(Math.random() * 4)]; + return { + data: log, + userId: user.id, + }; + }); setData(logData); setUsersList(filteredUsers); } }, [isLoading, userData]); useEffect(() => { - if ( - dev && - logsData?.data && - selectedUser && - lastProcessedUsername.current !== selectedUser.username - ) { - lastProcessedUsername.current = selectedUser.username || null; - onSearchTextSubmitted(selectedUser, data, logsData.data); - } + const username = selectedUser?.username; + + if (!dev || !logsData?.data || !username) return; + if (lastProcessedUsername.current === username) return; + + lastProcessedUsername.current = username; + onSearchTextSubmitted(selectedUser, data, logsData.data); }, [dev, logsData, selectedUser, data]); const isValidUsername = () => { diff --git a/src/pages/calendar/index.tsx b/src/pages/calendar/index.tsx index 65a7cfaf3..08f72c20b 100644 --- a/src/pages/calendar/index.tsx +++ b/src/pages/calendar/index.tsx @@ -11,6 +11,12 @@ import { OOO_REQUEST_DETAILS_URL } from '@/constants/url'; import { MONTHS } from '@/constants/calendar'; import { userDataType } from '@/interfaces/user.type'; +type ProcessCalendarData = [ + Map, + Map, + Map +]; + const UserStatusCalendar: FC = () => { const router = useRouter(); const { dev } = router.query; @@ -18,30 +24,21 @@ const UserStatusCalendar: FC = () => { const [selectedDate, onDateChange] = useState(new Date()); const [selectedUser, setSelectedUser] = useState(null); - const [processedData, setProcessedData] = useState< - [ - Record, - Record, - Record - ] - >([{}, {}, {}] as [ - Record, - Record, - Record + const [processedData, setProcessedData] = useState([ + new Map(), + new Map(), + new Map(), ]); - const [message, setMessage] = useState(null); const setTileClassName = ({ date }: { date: Date }) => { if (date.getDay() === 0) return 'sunday'; - // Check for OOO entries first (new API data) - if (processedData[2] && processedData[2][date.getTime()]) { + if (processedData[2].has(date.getTime())) { return 'OOO'; } - // Check for existing status (mock data) - return processedData[0] ? processedData[0][date.getTime()] : null; + return processedData[0].get(date.getTime()) || null; }; const formatOOOMessage = (oooEntries: OOOEntry[]): JSX.Element => { @@ -49,15 +46,7 @@ const UserStatusCalendar: FC = () => { <> {oooEntries.map((entry, index) => (
- {index > 0 && ( -
- )} + {index > 0 &&
}
From: {formatTimestampToDate(entry.from)}
Until: {formatTimestampToDate(entry.until)}
@@ -77,6 +66,24 @@ const UserStatusCalendar: FC = () => { ); }; + const formatOOODayMessage = ( + value: Date, + selectedUser: userDataType | null, + oooEntries: OOOEntry[] + ): JSX.Element => { + const dateStr = `${value.getDate()}-${ + MONTHS[value.getMonth()] + }-${value.getFullYear()}`; + const userLine = `${selectedUser?.username} is OOO on ${dateStr}`; + const oooDetails = formatOOOMessage(oooEntries); + return ( +
+
{userLine}
+
{oooDetails}
+
+ ); + }; + const handleDayClick = ( value: Date, event: React.MouseEvent @@ -90,20 +97,14 @@ const UserStatusCalendar: FC = () => { return; } - // Check for OOO entries first (new feature) - if (processedData[2] && processedData[2][value.getTime()]) { - const oooEntries = processedData[2][value.getTime()]; - const formattedMessage = formatOOOMessage(oooEntries); - setMessage( -
-
{`${ - selectedUser?.username - } is OOO on ${value.getDate()}-${ - MONTHS[value.getMonth()] - }-${value.getFullYear()}`}
-
{formattedMessage}
-
+ const oooEntries = processedData[2].get(value.getTime()); + if (oooEntries) { + const message = formatOOODayMessage( + value, + selectedUser, + oooEntries ); + setMessage(message); return; } @@ -123,13 +124,12 @@ const UserStatusCalendar: FC = () => { ); return; } - if (processedData[1] && processedData[1][value.getTime()]) { + const taskTitle = processedData[1].get(value.getTime()); + if (taskTitle) { setMessage( `${selectedUser?.username} is ACTIVE on ${value.getDate()}-${ MONTHS[value.getMonth()] - }-${value.getFullYear()} having task with title - ${ - processedData[1][value.getTime()] - }` + }-${value.getFullYear()} having task with title - ${taskTitle}` ); return; } @@ -156,13 +156,7 @@ const UserStatusCalendar: FC = () => { data, oooLogsData ); - setProcessedData( - processed as [ - Record, - Record, - Record - ] - ); + setProcessedData(processed); setMessage(null); }} loading={false} diff --git a/src/utils/userStatusCalendar.ts b/src/utils/userStatusCalendar.ts index 1156fe183..719c82f57 100644 --- a/src/utils/userStatusCalendar.ts +++ b/src/utils/userStatusCalendar.ts @@ -1,15 +1,10 @@ import { LogEntry } from '@/app/services/logsApi'; -interface LOG_TYPE { - userId: string; - data: []; -} - -interface LOG_DATA { +export interface LOG_DATA { status: string; startTime: number; endTime: number; - taskTitle: string; + taskTitle?: string; } export type OOOEntry = LogEntry; @@ -44,9 +39,9 @@ export const getDatesInRange = (startDate: Date, endDate: Date) => { export const processOOOLogsData = ( logsData: LogEntry[] -): [Record, Record] => { - const dictWithOOOEntries: Record = {}; - const dictWithTask: Record = {}; +): [Map, Map] => { + const dictWithOOOEntries = new Map(); + const dictWithTask = new Map(); logsData.forEach((logEntry: LogEntry) => { const dates = getDatesInRange( @@ -55,10 +50,12 @@ export const processOOOLogsData = ( ); dates.forEach((dateTimestamp) => { - if (!dictWithOOOEntries[dateTimestamp]) { - dictWithOOOEntries[dateTimestamp] = []; + const existingEntries = dictWithOOOEntries.get(dateTimestamp); + if (existingEntries) { + existingEntries.push(logEntry); + } else { + dictWithOOOEntries.set(dateTimestamp, [logEntry]); } - dictWithOOOEntries[dateTimestamp].push(logEntry); }); }); @@ -67,19 +64,21 @@ export const processOOOLogsData = ( export const processData = ( itemId: string | null, - data: [], + data: Array<{ userId: string; data: LOG_DATA[] }>, oooLogsData?: LogEntry[] -): [object, object, Record] => { +): [Map, Map, Map] => { if (!itemId) { - return [{}, {}, {}]; + return [new Map(), new Map(), new Map()]; } else { - const log: any = data.find((log: LOG_TYPE) => { - return log.userId === itemId; - }); + const log: { userId: string; data: LOG_DATA[] } | undefined = data.find( + (log: { userId: string; data: LOG_DATA[] }) => { + return log.userId === itemId; + } + ); - const dictWithStatus: Record = {}; - const dictWithTask: Record = {}; - const dictWithOOOEntries: Record = {}; + const dictWithStatus = new Map(); + const dictWithTask = new Map(); + const dictWithOOOEntries = new Map(); if (log && log.data?.length > 0) { log.data.forEach((logData: LOG_DATA) => { @@ -89,11 +88,14 @@ export const processData = ( ); if (logData.status === 'ACTIVE') { dates.forEach((dateTimestamp) => { - dictWithTask[dateTimestamp] = logData.taskTitle; + dictWithTask.set( + dateTimestamp, + logData.taskTitle || '' + ); }); } else { dates.forEach((dateTimestamp) => { - dictWithStatus[dateTimestamp] = logData.status; + dictWithStatus.set(dateTimestamp, logData.status); }); } }); @@ -101,10 +103,12 @@ export const processData = ( if (oooLogsData && oooLogsData.length > 0) { const [oooEntries] = processOOOLogsData(oooLogsData); - Object.assign(dictWithOOOEntries, oooEntries); + oooEntries.forEach((entries, dateTimestamp) => { + dictWithOOOEntries.set(dateTimestamp, entries); + }); - Object.keys(oooEntries).forEach((dateTimestamp) => { - dictWithStatus[parseInt(dateTimestamp)] = 'OOO'; + oooEntries.forEach((_, dateTimestamp) => { + dictWithStatus.set(dateTimestamp, 'OOO'); }); }