Skip to content

Fixes #2986 - Multiple UI Updates #3165

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

Merged
merged 8 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Expand Up @@ -72,6 +72,7 @@ function eventHeader({
id="dropdown-basic"
className={styles.dropdown}
data-testid="selectViewType"
style={{ width: '100%' }}
>
{viewType}
</Dropdown.Toggle>
Expand Down Expand Up @@ -100,6 +101,7 @@ function eventHeader({
id="dropdown-basic"
className={styles.dropdown}
data-testid="eventType"
style={{ width: '100%' }}
>
{t('eventType')}
</Dropdown.Toggle>
Expand Down
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';
Expand Down Expand Up @@ -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 */}
Expand Down
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();

Check warning on line 39 in src/components/OrgListCard/TruncatedText.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/OrgListCard/TruncatedText.tsx#L39

Added line #L39 was not covered by tests
}, 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);

Check warning on line 29 in src/components/OrgListCard/useDebounce.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/OrgListCard/useDebounce.tsx#L28-L29

Added lines #L28 - L29 were not covered by tests
}, 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
Expand Up @@ -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}`}
>
Expand Down
3 changes: 3 additions & 0 deletions src/screens/OrgList/OrgList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,9 @@ function orgList(): JSX.Element {
)}
</div>
</div>

{/* Text Infos for list */}

{!isLoading &&
(!orgsData?.organizationsConnection ||
orgsData.organizationsConnection.length === 0) &&
Expand Down Expand Up @@ -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>
Expand Down
56 changes: 45 additions & 11 deletions src/screens/OrganizationPeople/OrganizationPeople.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
},
},
Expand Down
Loading
Loading