-
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 3 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.
|
||
| 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 { 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'; | ||
| 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,8 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { | |
| const user = usersList.find( | ||
| (user: userDataType) => user.username === searchText | ||
| ); | ||
| setSelectedUser(user || null); | ||
| lastProcessedUsername.current = null; | ||
| onSearchTextSubmitted(user, data); | ||
| }; | ||
|
|
||
|
|
@@ -36,6 +52,17 @@ const SearchField = ({ onSearchTextSubmitted, loading }: SearchFieldProps) => { | |
| const [displayList, setDisplayList] = useState<userDataType[]>([]); | ||
| const [data, setData] = useState([]); | ||
|
|
||
| const queryParams = useMemo( | ||
| () => ({ | ||
| username: selectedUser?.username || '', | ||
| }), | ||
| [selectedUser?.username] | ||
| ); | ||
|
RishiChaubey31 marked this conversation as resolved.
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const { data: logsData } = useGetLogsByUsernameQuery(queryParams, { | ||
| skip: !dev || !selectedUser?.username, | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| if (userData?.users) { | ||
| const users: userDataType[] = userData.users; | ||
|
|
@@ -54,6 +81,18 @@ 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. |
||
|
|
||
| useEffect(() => { | ||
| if ( | ||
| dev && | ||
| logsData?.data && | ||
| selectedUser && | ||
| lastProcessedUsername.current !== selectedUser.username | ||
| ) { | ||
| lastProcessedUsername.current = selectedUser.username || null; | ||
| onSearchTextSubmitted(selectedUser, data, logsData.data); | ||
| } | ||
| }, [dev, logsData, selectedUser, data]); | ||
|
RishiChaubey31 marked this conversation as 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,86 @@ | ||
| 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[]): JSX.Element => { | ||
| return ( | ||
| <> | ||
| {oooEntries.map((entry, index) => ( | ||
| <div key={entry.requestId}> | ||
| {index > 0 && ( | ||
| <hr | ||
| style={{ | ||
| margin: '10px 0', | ||
| border: 'none', | ||
| borderTop: '1px solid #ccc', | ||
| }} | ||
| /> | ||
| )} | ||
| <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 handleDayClick = ( | ||
| value: Date, | ||
| event: React.MouseEvent<HTMLButtonElement> | ||
| ) => { | ||
| if (value.getDay() === 0) { | ||
| setMessage( | ||
| `${value.getDate()}-${ | ||
|
|
@@ -31,25 +89,43 @@ 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' }}>{formattedMessage}</div> | ||
| </div> | ||
| ); | ||
|
RishiChaubey31 marked this conversation as resolved.
Outdated
|
||
| 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 +136,7 @@ const UserStatusCalendar: FC = () => { | |
|
|
||
| setMessage( | ||
| `No user status found for ${ | ||
| selectedUser.username | ||
| selectedUser?.username | ||
| } on ${value.getDate()}-${ | ||
| MONTHS[value.getMonth()] | ||
| }-${value.getFullYear()}!` | ||
|
|
@@ -73,19 +149,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} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.