Skip to content

Commit 7407bfa

Browse files
AceHunterrcoderabbitai[bot]
authored andcommitted
* 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>
1 parent 82a53ce commit 7407bfa

File tree

10 files changed

+263
-25
lines changed

10 files changed

+263
-25
lines changed

src/assets/css/app.css

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/EventCalendar/EventCalendar.module.css

Whitespace-only changes.

src/components/EventCalendar/EventHeader.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ function eventHeader({
7272
id="dropdown-basic"
7373
className={styles.dropdown}
7474
data-testid="selectViewType"
75+
style={{ width: '100%' }}
7576
>
7677
{viewType}
7778
</Dropdown.Toggle>
@@ -100,6 +101,7 @@ function eventHeader({
100101
id="dropdown-basic"
101102
className={styles.dropdown}
102103
data-testid="eventType"
104+
style={{ width: '100%' }}
103105
>
104106
{t('eventType')}
105107
</Dropdown.Toggle>

src/components/OrgListCard/OrgListCard.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React from 'react';
2+
import TruncatedText from './TruncatedText';
3+
// import {useState} from 'react';
24
import FlaskIcon from 'assets/svgs/flask.svg?react';
35
import Button from 'react-bootstrap/Button';
46
import { useTranslation } from 'react-i18next';
@@ -94,17 +96,18 @@ function orgListCard(props: InterfaceOrgListCardProps): JSX.Element {
9496
<h4 className={`${styles.orgName} fw-semibold`}>{name}</h4>
9597
</Tooltip>
9698
{/* Description of the organization */}
97-
<h6 className={`${styles.orgdesc} fw-semibold`}>
98-
<span>{userData?.organizations[0].description}</span>
99-
</h6>
99+
<div className={`${styles.orgdesc} fw-semibold`}>
100+
<TruncatedText
101+
text={userData?.organizations[0]?.description || ''}
102+
/>
103+
</div>
104+
100105
{/* Display the organization address if available */}
101-
{address && address.city && (
106+
{address?.city && (
102107
<div className={styles.address}>
103-
<h6 className="text-secondary">
104-
<span className="address-line">{address.line1}, </span>
105-
<span className="address-line">{address.city}, </span>
106-
<span className="address-line">{address.countryCode}</span>
107-
</h6>
108+
<TruncatedText
109+
text={`${address?.line1}, ${address?.city}, ${address?.countryCode}`}
110+
/>
108111
</div>
109112
)}
110113
{/* Display the number of admins and members */}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import useDebounce from './useDebounce';
3+
4+
/**
5+
* Props for the `TruncatedText` component.
6+
*
7+
* Includes the text to be displayed and an optional maximum width override.
8+
*/
9+
interface InterfaceTruncatedTextProps {
10+
/** The full text to display. It may be truncated if it exceeds the maximum width. */
11+
text: string;
12+
/** Optional: Override the maximum width for truncation. */
13+
maxWidthOverride?: number;
14+
}
15+
16+
/**
17+
* A React functional component that displays text and truncates it with an ellipsis (`...`)
18+
* if the text exceeds the available width or the `maxWidthOverride` value.
19+
*
20+
* The component adjusts the truncation dynamically based on the available space
21+
* or the `maxWidthOverride` value. It also listens for window resize events to reapply truncation.
22+
*
23+
* @param props - The props for the component.
24+
* @returns A heading element (`<h6>`) containing the truncated or full text.
25+
*
26+
* @example
27+
* ```tsx
28+
* <TruncatedText text="This is a very long text" maxWidthOverride={150} />
29+
* ```
30+
*/
31+
const TruncatedText: React.FC<InterfaceTruncatedTextProps> = ({
32+
text,
33+
maxWidthOverride,
34+
}) => {
35+
const [truncatedText, setTruncatedText] = useState<string>('');
36+
const textRef = useRef<HTMLHeadingElement>(null);
37+
38+
const { debouncedCallback, cancel } = useDebounce(() => {
39+
truncateText();
40+
}, 100);
41+
42+
/**
43+
* Truncate the text based on the available width or the `maxWidthOverride` value.
44+
*/
45+
const truncateText = (): void => {
46+
const element = textRef.current;
47+
if (element) {
48+
const maxWidth = maxWidthOverride || element.offsetWidth;
49+
const fullText = text;
50+
51+
const computedStyle = getComputedStyle(element);
52+
const fontSize = parseFloat(computedStyle.fontSize);
53+
const charPerPx = 0.065 + fontSize * 0.002;
54+
const maxChars = Math.floor(maxWidth * charPerPx);
55+
56+
setTruncatedText(
57+
fullText.length > maxChars
58+
? `${fullText.slice(0, maxChars - 3)}...`
59+
: fullText,
60+
);
61+
}
62+
};
63+
64+
useEffect(() => {
65+
truncateText();
66+
window.addEventListener('resize', debouncedCallback);
67+
return () => {
68+
cancel();
69+
window.removeEventListener('resize', debouncedCallback);
70+
};
71+
}, [text, maxWidthOverride, debouncedCallback, cancel]);
72+
73+
return (
74+
<h6 ref={textRef} className="text-secondary">
75+
{truncatedText}
76+
</h6>
77+
);
78+
};
79+
80+
export default TruncatedText;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useRef, useCallback } from 'react';
2+
3+
/**
4+
* A custom React hook for debouncing a callback function.
5+
* It delays the execution of the callback until after a specified delay has elapsed
6+
* since the last time the debounced function was invoked.
7+
*
8+
* @param callback - The function to debounce.
9+
* @param delay - The delay in milliseconds to wait before invoking the callback.
10+
* @returns An object with the `debouncedCallback` function and a `cancel` method to clear the timeout.
11+
*/
12+
function useDebounce<T extends (...args: unknown[]) => void>(
13+
callback: T,
14+
delay: number,
15+
): { debouncedCallback: (...args: Parameters<T>) => void; cancel: () => void } {
16+
const timeoutRef = useRef<number | undefined>();
17+
18+
/**
19+
* The debounced version of the provided callback function.
20+
* This function resets the debounce timer on each call, ensuring the callback
21+
* is invoked only after the specified delay has elapsed without further calls.
22+
*
23+
* @param args - The arguments to pass to the callback when invoked.
24+
*/
25+
const debouncedCallback = useCallback(
26+
(...args: Parameters<T>) => {
27+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
28+
timeoutRef.current = window.setTimeout(() => {
29+
callback(...args);
30+
}, delay);
31+
},
32+
[callback, delay],
33+
);
34+
35+
const cancel = useCallback(() => {
36+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
37+
}, []);
38+
39+
return { debouncedCallback, cancel };
40+
}
41+
42+
export default useDebounce;

src/components/UsersTableItem/UsersTableItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ const UsersTableItem = (props: Props): JSX.Element => {
161161
<td>{user.user.email}</td>
162162
<td>
163163
<Button
164+
className="btn btn-success"
164165
onClick={() => setShowJoinedOrganizations(true)}
165166
data-testid={`showJoinedOrgsBtn${user.user._id}`}
166167
>

src/screens/OrgList/OrgList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,9 @@ function orgList(): JSX.Element {
398398
)}
399399
</div>
400400
</div>
401+
401402
{/* Text Infos for list */}
403+
402404
{!isLoading &&
403405
(!orgsData?.organizationsConnection ||
404406
orgsData.organizationsConnection.length === 0) &&
@@ -485,6 +487,7 @@ function orgList(): JSX.Element {
485487
<div
486488
className={`${styles.orgImgContainer} shimmer`}
487489
></div>
490+
488491
<div className={styles.content}>
489492
<h5 className="shimmer" title="Org name"></h5>
490493
<h6 className="shimmer" title="Location"></h6>

src/screens/OrganizationPeople/OrganizationPeople.tsx

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -184,18 +184,52 @@ function organizationPeople(): JSX.Element {
184184
headerAlign: 'center',
185185
headerClassName: `${styles.tableHeader}`,
186186
sortable: false,
187+
187188
renderCell: (params: GridCellParams) => {
188-
return params.row?.image ? (
189-
<img
190-
src={params.row?.image}
191-
alt="avatar"
192-
className={styles.TableImage}
193-
/>
194-
) : (
195-
<Avatar
196-
avatarStyle={styles.TableImage}
197-
name={`${params.row.firstName} ${params.row.lastName}`}
198-
/>
189+
// Fallback to a fixed width if computedWidth is unavailable
190+
const columnWidth = params.colDef.computedWidth || 150;
191+
const imageSize = Math.min(columnWidth * 0.6, 60); // Max size 40px, responsive scaling
192+
193+
return (
194+
<div
195+
style={{
196+
display: 'flex',
197+
justifyContent: 'center',
198+
alignItems: 'center',
199+
height: '100%',
200+
width: '100%',
201+
}}
202+
>
203+
{params.row?.image ? (
204+
<img
205+
src={params.row?.image}
206+
alt="avatar"
207+
style={{
208+
width: `${imageSize}px`,
209+
height: `${imageSize}px`,
210+
borderRadius: '50%',
211+
objectFit: 'cover',
212+
}}
213+
/>
214+
) : (
215+
<div
216+
style={{
217+
width: `${imageSize}px`,
218+
height: `${imageSize}px`,
219+
fontSize: `${imageSize * 0.4}px`,
220+
display: 'flex',
221+
alignItems: 'center',
222+
justifyContent: 'center',
223+
borderRadius: '50%',
224+
backgroundColor: '#ccc',
225+
}}
226+
>
227+
<Avatar
228+
name={`${params.row.firstName} ${params.row.lastName}`}
229+
/>
230+
</div>
231+
)}
232+
</div>
199233
);
200234
},
201235
},

0 commit comments

Comments
 (0)