-
Notifications
You must be signed in to change notification settings - Fork 167
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 all commits
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 { | ||
|
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.
|
||
| 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 = false, | ||
| format = 'feed', | ||
RishiChaubey31 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| type = 'REQUEST_CREATED', | ||
RishiChaubey31 marked this conversation as resolved.
Show resolved
Hide resolved
RishiChaubey31 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) => | ||
| `/logs?dev=${dev}&format=${format}&type=${type}&username=${username}`, | ||
RishiChaubey31 marked this conversation as resolved.
Show resolved
Hide resolved
RishiChaubey31 marked this conversation as resolved.
Show resolved
Hide 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,37 @@ | ||
| 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'; | ||
| import { LOG_DATA } from '@/utils/userStatusCalendar'; | ||
|
|
||
| type SearchFieldProps = { | ||
| onSearchTextSubmitted: (user: userDataType | undefined, data: any) => void; | ||
| onSearchTextSubmitted: ( | ||
| user: userDataType | undefined, | ||
| data: Array<{ userId: string; data: LOG_DATA[] }>, | ||
| 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,32 +43,54 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { | |
| const user = usersList.find( | ||
| (user: userDataType) => user.username === searchText | ||
| ); | ||
| setSelectedUser(user || null); | ||
| lastProcessedUsername.current = null; | ||
| onSearchTextSubmitted(user, data); | ||
| }; | ||
|
|
||
| const { data: userData, isError, isLoading } = useGetAllUsersQuery(); | ||
| const [usersList, setUsersList] = useState<userDataType[]>([]); | ||
| const [displayList, setDisplayList] = useState<userDataType[]>([]); | ||
| const [data, setData] = useState([]); | ||
| const [data, setData] = useState< | ||
| Array<{ userId: string; data: LOG_DATA[] }> | ||
|
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. define it type please
|
||
| >([]); | ||
|
|
||
| const { data: logsData } = useGetLogsByUsernameQuery( | ||
| { username: selectedUser?.username || '' }, | ||
| { | ||
| 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 { | ||
|
Comment on lines
+73
to
+74
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. also what is this math.random doing here |
||
| data: log, | ||
| userId: user.id, | ||
| }; | ||
| }); | ||
| setData(logData); | ||
| setUsersList(filteredUsers); | ||
| } | ||
| }, [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. |
||
|
|
||
| useEffect(() => { | ||
| 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]); | ||
RishiChaubey31 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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,93 @@ | ||
| 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'; | ||
|
|
||
| type ProcessCalendarData = [ | ||
| Map<number, string>, | ||
| Map<number, string>, | ||
| Map<number, OOOEntry[]> | ||
| ]; | ||
|
|
||
| const UserStatusCalendar: FC = () => { | ||
| const [selectedDate, onDateChange] = useState<Date>(new Date()); | ||
| const [selectedUser, setSelectedUser]: any = useState(null); | ||
| const [processedData, setProcessedData] = useState<any>( | ||
| processData(selectedUser ? selectedUser.id : null, []) | ||
| ); | ||
| const router = useRouter(); | ||
| const { dev } = router.query; | ||
| const isDevMode = dev === 'true'; | ||
|
|
||
| const [message, setMessage]: any = useState(null); | ||
| const [loading, setLoading]: any = useState(false); | ||
| 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] = useState<userDataType | null>(null); | ||
| const [processedData, setProcessedData] = useState<ProcessCalendarData>([ | ||
| new Map(), | ||
| new Map(), | ||
| new Map(), | ||
| ]); | ||
| 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'; | ||
| return processedData[0] ? processedData[0][date.getTime()] : null; | ||
|
|
||
| if (processedData[2].has(date.getTime())) { | ||
|
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. where is this processedData[2] , like what is the point of creating map |
||
| return 'OOO'; | ||
| } | ||
|
|
||
| return processedData[0].get(date.getTime()) || null; | ||
| }; | ||
|
|
||
| const formatOOOMessage = (oooEntries: OOOEntry[]): JSX.Element => { | ||
| return ( | ||
| <> | ||
| {oooEntries.map((entry, index) => ( | ||
| <div key={entry.requestId}> | ||
| {index > 0 && <div style={{ marginTop: '10px' }} />} | ||
| <div>From: {formatTimestampToDate(entry.from)}</div> | ||
| <div>Until: {formatTimestampToDate(entry.until)}</div> | ||
| <div> | ||
| Request ID:{' '} | ||
| <a | ||
| href={`${OOO_REQUEST_DETAILS_URL}${entry.requestId}`} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| {entry.requestId} | ||
| </a> | ||
| </div> | ||
| {entry.message && <div>Message: {entry.message}</div>} | ||
| </div> | ||
| ))} | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| 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 ( | ||
| <div> | ||
| <div>{userLine}</div> | ||
| <div style={{ marginTop: '10px' }}>{oooDetails}</div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| const handleDayClick = (value: Date, event: any) => { | ||
| const handleDayClick = ( | ||
| value: Date, | ||
| event: React.MouseEvent<HTMLButtonElement> | ||
| ) => { | ||
| if (value.getDay() === 0) { | ||
| setMessage( | ||
| `${value.getDate()}-${ | ||
|
|
@@ -31,36 +96,47 @@ const UserStatusCalendar: FC = () => { | |
| ); | ||
| return; | ||
| } | ||
|
|
||
| const oooEntries = processedData[2].get(value.getTime()); | ||
| if (oooEntries) { | ||
| const message = formatOOODayMessage( | ||
|
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.
|
||
| value, | ||
| selectedUser, | ||
| oooEntries | ||
| ); | ||
| setMessage(message); | ||
| 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()]) { | ||
| const taskTitle = processedData[1].get(value.getTime()); | ||
| if (taskTitle) { | ||
| 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()] | ||
| }` | ||
| }-${value.getFullYear()} having task with title - ${taskTitle}` | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| setMessage( | ||
| `No user status found for ${ | ||
| selectedUser.username | ||
| selectedUser?.username | ||
| } on ${value.getDate()}-${ | ||
| MONTHS[value.getMonth()] | ||
| }-${value.getFullYear()}!` | ||
|
|
@@ -73,19 +149,27 @@ const UserStatusCalendar: FC = () => { | |
|
|
||
| <div className="container calendar-container"> | ||
| <SearchField | ||
| onSearchTextSubmitted={(user, data) => { | ||
| setSelectedUser(user); | ||
| setProcessedData( | ||
| processData(user ? user.id : null, data) | ||
| onSearchTextSubmitted={(user, data, oooLogsData) => { | ||
| setSelectedUser(user || null); | ||
| const processed = processData( | ||
| user ? user.id : null, | ||
| data, | ||
| oooLogsData | ||
| ); | ||
| setProcessedData(processed); | ||
| 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} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.