Skip to content
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

prevented unnecessary page reload with complementary test #3202

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fixes #2986 - Multiple UI Updates (#3165)
* UI fixes on organisation pages

* Added TSDoc for Truncated Text

* Added Debouncer

* Update src/components/OrgListCard/OrgListCard.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Added code rabbit suggestions

* Fixed test error

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2 people authored and Dhiren-Mhatre committed Jan 9, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 7407bfaf0d6ccb8adf68e0fe676e98a686615207
21 changes: 21 additions & 0 deletions src/assets/css/app.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
2 changes: 2 additions & 0 deletions src/components/EventCalendar/EventHeader.tsx
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@ function eventHeader({
id="dropdown-basic"
className={styles.dropdown}
data-testid="selectViewType"
style={{ width: '100%' }}
>
{viewType}
</Dropdown.Toggle>
@@ -100,6 +101,7 @@ function eventHeader({
id="dropdown-basic"
className={styles.dropdown}
data-testid="eventType"
style={{ width: '100%' }}
>
{t('eventType')}
</Dropdown.Toggle>
21 changes: 12 additions & 9 deletions src/components/OrgListCard/OrgListCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import TruncatedText from './TruncatedText';
// import {useState} from 'react';
import FlaskIcon from 'assets/svgs/flask.svg?react';
import Button from 'react-bootstrap/Button';
import { useTranslation } from 'react-i18next';
@@ -94,17 +96,18 @@ function orgListCard(props: InterfaceOrgListCardProps): JSX.Element {
<h4 className={`${styles.orgName} fw-semibold`}>{name}</h4>
</Tooltip>
{/* Description of the organization */}
<h6 className={`${styles.orgdesc} fw-semibold`}>
<span>{userData?.organizations[0].description}</span>
</h6>
<div className={`${styles.orgdesc} fw-semibold`}>
<TruncatedText
text={userData?.organizations[0]?.description || ''}
/>
</div>

{/* Display the organization address if available */}
{address && address.city && (
{address?.city && (
<div className={styles.address}>
<h6 className="text-secondary">
<span className="address-line">{address.line1}, </span>
<span className="address-line">{address.city}, </span>
<span className="address-line">{address.countryCode}</span>
</h6>
<TruncatedText
text={`${address?.line1}, ${address?.city}, ${address?.countryCode}`}
/>
</div>
)}
{/* Display the number of admins and members */}
80 changes: 80 additions & 0 deletions src/components/OrgListCard/TruncatedText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useState, useEffect, useRef } from 'react';
import useDebounce from './useDebounce';

/**
* Props for the `TruncatedText` component.
*
* Includes the text to be displayed and an optional maximum width override.
*/
interface InterfaceTruncatedTextProps {
/** The full text to display. It may be truncated if it exceeds the maximum width. */
text: string;
/** Optional: Override the maximum width for truncation. */
maxWidthOverride?: number;
}

/**
* A React functional component that displays text and truncates it with an ellipsis (`...`)
* if the text exceeds the available width or the `maxWidthOverride` value.
*
* The component adjusts the truncation dynamically based on the available space
* or the `maxWidthOverride` value. It also listens for window resize events to reapply truncation.
*
* @param props - The props for the component.
* @returns A heading element (`<h6>`) containing the truncated or full text.
*
* @example
* ```tsx
* <TruncatedText text="This is a very long text" maxWidthOverride={150} />
* ```
*/
const TruncatedText: React.FC<InterfaceTruncatedTextProps> = ({
text,
maxWidthOverride,
}) => {
const [truncatedText, setTruncatedText] = useState<string>('');
const textRef = useRef<HTMLHeadingElement>(null);

const { debouncedCallback, cancel } = useDebounce(() => {
truncateText();
}, 100);

/**
* Truncate the text based on the available width or the `maxWidthOverride` value.
*/
const truncateText = (): void => {
const element = textRef.current;
if (element) {
const maxWidth = maxWidthOverride || element.offsetWidth;
const fullText = text;

const computedStyle = getComputedStyle(element);
const fontSize = parseFloat(computedStyle.fontSize);
const charPerPx = 0.065 + fontSize * 0.002;
const maxChars = Math.floor(maxWidth * charPerPx);

setTruncatedText(
fullText.length > maxChars
? `${fullText.slice(0, maxChars - 3)}...`
: fullText,
);
}
};

useEffect(() => {
truncateText();
window.addEventListener('resize', debouncedCallback);
return () => {
cancel();
window.removeEventListener('resize', debouncedCallback);
};
}, [text, maxWidthOverride, debouncedCallback, cancel]);

return (
<h6 ref={textRef} className="text-secondary">
{truncatedText}
</h6>
);
};

export default TruncatedText;
42 changes: 42 additions & 0 deletions src/components/OrgListCard/useDebounce.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useRef, useCallback } from 'react';

/**
* A custom React hook for debouncing a callback function.
* It delays the execution of the callback until after a specified delay has elapsed
* since the last time the debounced function was invoked.
*
* @param callback - The function to debounce.
* @param delay - The delay in milliseconds to wait before invoking the callback.
* @returns An object with the `debouncedCallback` function and a `cancel` method to clear the timeout.
*/
function useDebounce<T extends (...args: unknown[]) => void>(
callback: T,
delay: number,
): { debouncedCallback: (...args: Parameters<T>) => void; cancel: () => void } {
const timeoutRef = useRef<number | undefined>();

/**
* The debounced version of the provided callback function.
* This function resets the debounce timer on each call, ensuring the callback
* is invoked only after the specified delay has elapsed without further calls.
*
* @param args - The arguments to pass to the callback when invoked.
*/
const debouncedCallback = useCallback(
(...args: Parameters<T>) => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = window.setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay],
);

const cancel = useCallback(() => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
}, []);

return { debouncedCallback, cancel };
}

export default useDebounce;
1 change: 1 addition & 0 deletions src/components/UsersTableItem/UsersTableItem.tsx
Original file line number Diff line number Diff line change
@@ -161,6 +161,7 @@ const UsersTableItem = (props: Props): JSX.Element => {
<td>{user.user.email}</td>
<td>
<Button
className="btn btn-success"
onClick={() => setShowJoinedOrganizations(true)}
data-testid={`showJoinedOrgsBtn${user.user._id}`}
>
3 changes: 3 additions & 0 deletions src/screens/OrgList/OrgList.tsx
Original file line number Diff line number Diff line change
@@ -398,7 +398,9 @@ function orgList(): JSX.Element {
)}
</div>
</div>

{/* Text Infos for list */}

{!isLoading &&
(!orgsData?.organizationsConnection ||
orgsData.organizationsConnection.length === 0) &&
@@ -485,6 +487,7 @@ function orgList(): JSX.Element {
<div
className={`${styles.orgImgContainer} shimmer`}
></div>

<div className={styles.content}>
<h5 className="shimmer" title="Org name"></h5>
<h6 className="shimmer" title="Location"></h6>
56 changes: 45 additions & 11 deletions src/screens/OrganizationPeople/OrganizationPeople.tsx
Original file line number Diff line number Diff line change
@@ -184,18 +184,52 @@ function organizationPeople(): JSX.Element {
headerAlign: 'center',
headerClassName: `${styles.tableHeader}`,
sortable: false,

renderCell: (params: GridCellParams) => {
return params.row?.image ? (
<img
src={params.row?.image}
alt="avatar"
className={styles.TableImage}
/>
) : (
<Avatar
avatarStyle={styles.TableImage}
name={`${params.row.firstName} ${params.row.lastName}`}
/>
// Fallback to a fixed width if computedWidth is unavailable
const columnWidth = params.colDef.computedWidth || 150;
const imageSize = Math.min(columnWidth * 0.6, 60); // Max size 40px, responsive scaling

return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
width: '100%',
}}
>
{params.row?.image ? (
<img
src={params.row?.image}
alt="avatar"
style={{
width: `${imageSize}px`,
height: `${imageSize}px`,
borderRadius: '50%',
objectFit: 'cover',
}}
/>
) : (
<div
style={{
width: `${imageSize}px`,
height: `${imageSize}px`,
fontSize: `${imageSize * 0.4}px`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
backgroundColor: '#ccc',
}}
>
<Avatar
name={`${params.row.firstName} ${params.row.lastName}`}
/>
</div>
)}
</div>
);
},
},
Loading