-
Notifications
You must be signed in to change notification settings - Fork 162
feature : Show OOO users data on Calendar #1378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<LogsResponse, LogsQueryArgs>({ | ||
| query: ({ | ||
| username, | ||
| dev = true, | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
| format = 'feed', | ||
|
RishiChaubey31 marked this conversation as resolved.
|
||
| type = 'REQUEST_CREATED', | ||
|
RishiChaubey31 marked this conversation as resolved.
RishiChaubey31 marked this conversation as resolved.
|
||
| }) => | ||
| `/logs?dev=${dev}&format=${format}&type=${type}&username=${username}`, | ||
|
RishiChaubey31 marked this conversation as resolved.
RishiChaubey31 marked this conversation as resolved.
|
||
| providesTags: ['Logs'], | ||
| }), | ||
| }), | ||
| }); | ||
|
|
||
| export const { useGetLogsByUsernameQuery } = logsApi; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
| 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<string>(''); | ||
| const [selectedUser, setSelectedUser] = useState<userDataType | null>(null); | ||
| const lastProcessedUsername = useRef<string | null>(null); | ||
|
|
||
| const onSearchTextChanged = (e: ChangeEvent<HTMLInputElement>) => { | ||
| 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<userDataType[]>([]); | ||
| 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 } | ||
| ); | ||
|
RishiChaubey31 marked this conversation as resolved.
|
||
|
|
||
| useEffect(() => { | ||
| if (userData?.users) { | ||
| const users: userDataType[] = userData.users; | ||
|
|
@@ -54,6 +77,19 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { | |
| } | ||
| }, [isLoading, userData]); | ||
|
Comment on lines
65
to
82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this file, it seems that we’re currently fetching all users from the database and then filtering them locally. Instead of doing that, I’d suggest using the user search API to fetch users directly based on their type. For example, if we have 200 active users in the database but the getAllUsers only returns 100 users due to pagination, we might never find the intended user. Using the search API will make this process more efficient and reliable. |
||
|
|
||
| // 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]); | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const isValidUsername = () => { | ||
| const usernames = usersList.map((user: userDataType) => user.username); | ||
| if (usernames.includes(searchText)) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Date>(new Date()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const [selectedUser, setSelectedUser]: any = useState(null); | ||
| const [processedData, setProcessedData] = useState<any>( | ||
| processData(selectedUser ? selectedUser.id : null, []) | ||
| ); | ||
| const [selectedUser, setSelectedUser] = useState<userDataType | null>(null); | ||
| const [processedData, setProcessedData] = useState< | ||
| [ | ||
| Record<number, string>, | ||
| Record<number, string>, | ||
| Record<number, OOOEntry[]> | ||
| ] | ||
| >([{}, {}, {}] as [ | ||
| Record<number, string>, | ||
| Record<number, string>, | ||
| Record<number, OOOEntry[]> | ||
| ]); | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const [message, setMessage]: any = useState(null); | ||
| const [loading, setLoading]: any = useState(false); | ||
| const [message, setMessage] = useState<string | JSX.Element | null>(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 += `<a href="${requestLink}" target="_blank" rel="noopener noreferrer">${entry.requestId}</a>`; | ||
|
|
||
| if (entry.message) { | ||
| messageText += `\nMessage: ${entry.message}`; | ||
| } | ||
|
|
||
| return messageText; | ||
| }) | ||
| .join('\n\n---\n\n'); | ||
| }; | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const handleDayClick = ( | ||
| value: Date, | ||
| event: React.MouseEvent<HTMLButtonElement> | ||
| ) => { | ||
| if (value.getDay() === 0) { | ||
| setMessage( | ||
| `${value.getDate()}-${ | ||
|
|
@@ -31,25 +75,46 @@ const UserStatusCalendar: FC = () => { | |
| ); | ||
| return; | ||
| } | ||
|
|
||
| // Check for OOO entries first (new feature) | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
| if (processedData[2] && processedData[2][value.getTime()]) { | ||
| const oooEntries = processedData[2][value.getTime()]; | ||
| const formattedMessage = formatOOOMessage(oooEntries); | ||
| setMessage( | ||
| <div> | ||
| <div>{`${ | ||
| selectedUser?.username | ||
| } is OOO on ${value.getDate()}-${ | ||
| MONTHS[value.getMonth()] | ||
| }-${value.getFullYear()}`}</div> | ||
| <div | ||
| style={{ marginTop: '10px', whiteSpace: 'pre-line' }} | ||
| dangerouslySetInnerHTML={{ __html: formattedMessage }} | ||
| /> | ||
| </div> | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| if (event.currentTarget.classList.contains('OOO')) { | ||
| setMessage( | ||
| `${selectedUser.username} is OOO on ${value.getDate()}-${ | ||
| `${selectedUser?.username} is OOO on ${value.getDate()}-${ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| MONTHS[value.getMonth()] | ||
| }-${value.getFullYear()}` | ||
| ); | ||
| return; | ||
| } | ||
| 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()}` | ||
| ); | ||
| return; | ||
| } | ||
| 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 = () => { | |
|
|
||
| <div className="container calendar-container"> | ||
| <SearchField | ||
| onSearchTextSubmitted={(user, data) => { | ||
| 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<number, string>, | ||
| Record<number, string>, | ||
| Record<number, OOOEntry[]> | ||
| ] | ||
| ); | ||
| setMessage(null); | ||
| }} | ||
| loading={loading} | ||
| loading={false} | ||
| dev={isDevMode} | ||
| /> | ||
| {selectedUser && ( | ||
| <div className="calendar" data-testid="react-calendar"> | ||
| <Calendar | ||
| onChange={onDateChange as any} | ||
| onChange={(value) => { | ||
| if (value instanceof Date) { | ||
| onDateChange(value); | ||
| } | ||
| }} | ||
| className="calendar-div" | ||
| value={selectedDate} | ||
| onClickDay={handleDayClick} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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', | ||
| ]; | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
|
|
||
| return `${ | ||
| months[date.getMonth()] | ||
| } ${date.getDate()}, ${date.getFullYear()}`; | ||
| }; | ||
|
Comment on lines
+26
to
+47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
export const formatTimestampToDate = (timestamp: number): string => { |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.