Skip to content

Commit fb1cc20

Browse files
committed
Fix for handles not showing with submissions in review app for large challenges and better group searching in system admin app
1 parent de6b71d commit fb1cc20

File tree

4 files changed

+127
-35
lines changed

4 files changed

+127
-35
lines changed

src/apps/admin/src/permission-management/PermissionGroupsPage/PermissionGroupsPage.module.scss

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,29 @@
1919
text-align: center;
2020
}
2121

22-
.btnNewGroup {
23-
margin: $sp-8 $sp-8 $sp-4 $sp-8;
22+
.actions {
23+
display: flex;
24+
align-items: flex-end;
25+
gap: $sp-4;
26+
margin: $sp-8 $sp-8 $sp-4;
2427

2528
@include ltelg {
29+
flex-direction: column;
30+
align-items: stretch;
2631
margin: $sp-4;
32+
gap: $sp-3;
33+
}
34+
}
35+
36+
.searchField {
37+
flex: 1;
38+
}
39+
40+
.btnNewGroup {
41+
margin: 0;
42+
min-width: max-content;
43+
44+
@include ltelg {
45+
width: 100%;
2746
}
2847
}

src/apps/admin/src/permission-management/PermissionGroupsPage/PermissionGroupsPage.tsx

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
22
* Permission groups page.
33
*/
4-
import { FC, useContext, useState } from 'react'
4+
import { ChangeEvent, FC, useContext, useMemo, useState } from 'react'
55
import classNames from 'classnames'
66

7-
import { Button, LoadingSpinner, PageTitle } from '~/libs/ui'
7+
import { Button, InputText, LoadingSpinner, PageTitle } from '~/libs/ui'
88
import { PlusIcon } from '@heroicons/react/solid'
99

1010
import { DialogAddGroup } from '../../lib/components/DialogAddGroup'
@@ -24,6 +24,7 @@ const pageTitle = 'Groups'
2424

2525
export const PermissionGroupsPage: FC<Props> = (props: Props) => {
2626
const [openDialogAddGroup, setOpenDialogAddGroup] = useState(false)
27+
const [searchTerm, setSearchTerm] = useState('')
2728
const { loadUser, cancelLoadUser, usersMapping }: AdminAppContextType
2829
= useContext(AdminAppContext)
2930
const {
@@ -37,6 +38,26 @@ export const PermissionGroupsPage: FC<Props> = (props: Props) => {
3738
usersMapping,
3839
)
3940

41+
const filteredGroups = useMemo(() => {
42+
const normalized = searchTerm
43+
.trim()
44+
.toLowerCase()
45+
if (!normalized) {
46+
return groups
47+
}
48+
49+
return groups.filter(group => {
50+
const id = group.id ? group.id.toLowerCase() : ''
51+
const name = group.name ? group.name.toLowerCase() : ''
52+
53+
return id.includes(normalized) || name.includes(normalized)
54+
})
55+
}, [groups, searchTerm])
56+
const hasSearchTerm = useMemo(
57+
() => searchTerm.trim().length > 0,
58+
[searchTerm],
59+
)
60+
4061
return (
4162
<div className={classNames(styles.container, props.className)}>
4263
<PageTitle>{pageTitle}</PageTitle>
@@ -51,24 +72,40 @@ export const PermissionGroupsPage: FC<Props> = (props: Props) => {
5172
</div>
5273
) : (
5374
<>
54-
<Button
55-
primary
56-
size='lg'
57-
icon={PlusIcon}
58-
iconToLeft
59-
label='new group'
60-
onClick={function onClick() {
61-
setOpenDialogAddGroup(true)
62-
}}
63-
className={styles.btnNewGroup}
64-
/>
65-
{groups.length === 0 ? (
75+
<div className={styles.actions}>
76+
<InputText
77+
name='groupSearch'
78+
type='text'
79+
label='Search groups'
80+
placeholder='Search by name or ID'
81+
value={searchTerm}
82+
onChange={function onChange(event: ChangeEvent<HTMLInputElement>) {
83+
setSearchTerm(event.target.value)
84+
}}
85+
forceUpdateValue
86+
classNameWrapper={styles.searchField}
87+
/>
88+
<Button
89+
primary
90+
size='lg'
91+
icon={PlusIcon}
92+
iconToLeft
93+
label='new group'
94+
onClick={function onClick() {
95+
setOpenDialogAddGroup(true)
96+
}}
97+
className={styles.btnNewGroup}
98+
/>
99+
</div>
100+
{filteredGroups.length === 0 ? (
66101
<p className={styles.noRecordFound}>
67-
{MSG_NO_RECORD_FOUND}
102+
{hasSearchTerm
103+
? 'No groups match your search.'
104+
: MSG_NO_RECORD_FOUND}
68105
</p>
69106
) : (
70107
<GroupsTable
71-
datas={groups}
108+
datas={filteredGroups}
72109
usersMapping={usersMapping}
73110
/>
74111
)}

src/apps/review/src/lib/components/ChallengePhaseInfo/ChallengePhaseInfo.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ type ChallengePhaseItem = ChallengePhaseDisplayItem | ChallengePhaseProgressItem
4848

4949
type ChallengeVariant = 'active' | 'past'
5050

51+
interface PhaseDisplayInfo {
52+
phaseEndDateString?: string
53+
phaseLabel: string
54+
timeLeft?: string
55+
timeLeftColor?: string
56+
timeLeftStatus?: string
57+
}
58+
5159
export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
5260
const { myChallengeRoles }: useRoleProps = useRole()
5361
const {
@@ -157,17 +165,16 @@ export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
157165
isLoadingPayment,
158166
isTopgearTask,
159167
])
160-
const phaseDisplayInfo = useMemo(
161-
() => computePhaseDisplayInfo(props.challengeInfo),
162-
[props.challengeInfo],
163-
)
164168
const {
165169
phaseEndDateString: displayPhaseEndDateString,
166170
phaseLabel: displayPhaseLabel,
167171
timeLeft: displayTimeLeft,
168172
timeLeftColor: displayTimeLeftColor,
169173
timeLeftStatus: displayTimeLeftStatus,
170-
} = phaseDisplayInfo
174+
}: PhaseDisplayInfo = useMemo(
175+
() => computePhaseDisplayInfo(props.challengeInfo),
176+
[props.challengeInfo],
177+
)
171178

172179
useEffect(() => {
173180
const run = async (): Promise<void> => {
@@ -489,14 +496,6 @@ function createActiveItems(config: {
489496
return items
490497
}
491498

492-
interface PhaseDisplayInfo {
493-
phaseEndDateString?: string
494-
phaseLabel: string
495-
timeLeft?: string
496-
timeLeftColor?: string
497-
timeLeftStatus?: string
498-
}
499-
500499
function computePhaseDisplayInfo(data?: ChallengeInfo): PhaseDisplayInfo {
501500
const fallbackPhaseLabel = (data?.currentPhase || '').trim() || 'N/A'
502501
const fallback: PhaseDisplayInfo = {

src/apps/review/src/lib/services/resources.service.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from '../models'
1818

1919
const resourceBaseUrl = `${EnvironmentConfig.API.V6}`
20+
const DEFAULT_PER_PAGE = 1000
2021

2122
/**
2223
* Fetch all resource roles
@@ -38,13 +39,49 @@ export const fetchResources = async (query: {
3839
challengeId?: string
3940
memberId?: string
4041
}): Promise<PaginatedResponse<BackendResource[]>> => {
41-
const results = await xhrGetPaginatedAsync<BackendResource[]>(
42-
`${resourceBaseUrl}/resources?${qs.stringify(query)}`,
42+
const baseQuery = {
43+
...query,
44+
page: 1,
45+
perPage: DEFAULT_PER_PAGE,
46+
}
47+
const firstPage = await xhrGetPaginatedAsync<BackendResource[]>(
48+
`${resourceBaseUrl}/resources?${qs.stringify(baseQuery)}`,
4349
)
50+
const totalPages = firstPage.totalPages ?? 0
51+
52+
if (totalPages <= 1) {
53+
return {
54+
...firstPage,
55+
data: firstPage.data
56+
.map(adjustBackendResource)
57+
.filter((resource): resource is BackendResource => Boolean(resource)),
58+
}
59+
}
60+
61+
const remainingPagePromises: Array<Promise<PaginatedResponse<BackendResource[]>>> = []
62+
for (let page = 2; page <= totalPages; page += 1) {
63+
remainingPagePromises.push(
64+
xhrGetPaginatedAsync<BackendResource[]>(
65+
`${resourceBaseUrl}/resources?${qs.stringify({
66+
...query,
67+
page,
68+
perPage: DEFAULT_PER_PAGE,
69+
})}`,
70+
),
71+
)
72+
}
73+
74+
const remainingPages = await Promise.all(remainingPagePromises)
75+
const combinedData = [
76+
...firstPage.data,
77+
...remainingPages.flatMap(pageResult => pageResult.data),
78+
]
4479

4580
return {
46-
...results,
47-
data: results.data.map(adjustBackendResource) as BackendResource[],
81+
...firstPage,
82+
data: combinedData
83+
.map(adjustBackendResource)
84+
.filter((resource): resource is BackendResource => Boolean(resource)),
4885
}
4986
}
5087

0 commit comments

Comments
 (0)