Skip to content

Commit 22b4670

Browse files
committed
fix: split enrollment page on status for pagination
1 parent 0b539c3 commit 22b4670

File tree

3 files changed

+100
-65
lines changed

3 files changed

+100
-65
lines changed

backend/src/database/class_enrollments.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,11 @@ func (db *DB) GetProgramClassEnrollmentsForProgram(args *models.QueryContext, cl
336336
Joins("LEFT JOIN program_completions pc ON pc.user_id = pse.user_id AND pc.program_class_id = ?", classId).
337337
Where("pse.class_id = ?", classId)
338338
if status != "" {
339-
tx = tx.Where("pse.enrollment_status ILIKE ?", status)
339+
if status == "not_enrolled" {
340+
tx = tx.Where("pse.enrollment_status != ?", "Enrolled")
341+
} else {
342+
tx = tx.Where("pse.enrollment_status ILIKE ?", status)
343+
}
340344
}
341345
if search != "" {
342346
tx = tx.Where("u.name_last ILIKE ? OR u.name_first ILIKE ? OR u.doc_id ILIKE ?", search, search, search)

frontend/src/Components/ClassEnrollmentDetailsTable.tsx

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ interface EnrollmentTableProps {
1313
isEditable: (enrollment: ClassEnrollment) => boolean;
1414
handleChange: (value: string, enrollment: ClassEnrollment) => void;
1515
handleShowCompletionDetails: (enrollment: ClassEnrollment) => Promise<void>;
16-
showOthers: boolean;
17-
setShowOthers: (show: boolean) => void;
16+
viewMode: 'enrolled' | 'other';
1817
classInfo?: Class;
1918
onEnrollmentUpdate: () => void;
2019
}
@@ -30,8 +29,7 @@ const ClassEnrollmentDetailsTable: React.FC<EnrollmentTableProps> = ({
3029
isEditable,
3130
handleChange,
3231
handleShowCompletionDetails,
33-
showOthers,
34-
setShowOthers,
32+
viewMode,
3533
classInfo,
3634
onEnrollmentUpdate
3735
}) => {
@@ -40,17 +38,12 @@ const ClassEnrollmentDetailsTable: React.FC<EnrollmentTableProps> = ({
4038
? status.replace('Incomplete: ', '')
4139
: status;
4240

43-
const enrolledStudents = enrollments.filter(
44-
(e) => e.enrollment_status === EnrollmentStatus.Enrolled
45-
);
46-
const otherStatusStudents = enrollments.filter(
47-
(e) => e.enrollment_status !== EnrollmentStatus.Enrolled
48-
);
41+
const isEnrolledView = viewMode === 'enrolled';
4942

50-
const renderTableHeader = (enrolled: boolean) => (
43+
const renderTableHeader = () => (
5144
<tr className="grid grid-cols-6 justify-items-start pb-4">
5245
<th className="justify-self-start pl-4">
53-
{enrolled && (
46+
{isEnrolledView && (
5447
<input
5548
className="checkbox"
5649
type="checkbox"
@@ -67,10 +60,10 @@ const ClassEnrollmentDetailsTable: React.FC<EnrollmentTableProps> = ({
6760
</tr>
6861
);
6962

70-
const renderTableBody = (rows: ClassEnrollment[], enrolled: boolean) => (
63+
const renderTableBody = () => (
7164
<>
72-
{rows.length > 0 ? (
73-
rows.map((enrollment) => (
65+
{enrollments.length > 0 ? (
66+
enrollments.map((enrollment) => (
7467
<tr
7568
key={enrollment.id}
7669
className={`card h-16 w-full grid-cols-6 justify-items-start cursor-pointer ${
@@ -83,7 +76,7 @@ const ClassEnrollmentDetailsTable: React.FC<EnrollmentTableProps> = ({
8376
}}
8477
>
8578
<td className="justify-self-start pl-4">
86-
{enrolled && (
79+
{isEnrolledView && (
8780
<input
8881
className="checkbox justify-self-start"
8982
type="checkbox"
@@ -178,34 +171,15 @@ const ClassEnrollmentDetailsTable: React.FC<EnrollmentTableProps> = ({
178171

179172
return (
180173
<div className="relative w-full" style={{ overflowX: 'clip' }}>
181-
<h2 className="text-xl font-bold mb-2">Enrolled Students</h2>
174+
<h2 className="text-xl font-bold mb-2">
175+
{isEnrolledView
176+
? 'Enrolled Students'
177+
: 'Graduated/Incomplete Status Students'}
178+
</h2>
182179
<table className="table-2 mb-4">
183-
<thead>{renderTableHeader(true)}</thead>
184-
<tbody>{renderTableBody(enrolledStudents, true)}</tbody>
180+
<thead>{renderTableHeader()}</thead>
181+
<tbody>{renderTableBody()}</tbody>
185182
</table>
186-
{otherStatusStudents.length > 0 && (
187-
<button
188-
type="button"
189-
className="button-outline mb-2"
190-
onClick={() => setShowOthers(!showOthers)}
191-
>
192-
{showOthers ? 'Hide Other' : 'Show Other'}
193-
</button>
194-
)}
195-
196-
{showOthers && (
197-
<>
198-
<h2 className="text-xl font-bold my-2">
199-
Graduated/Incomplete Status Students
200-
</h2>
201-
<table className="table-2 mb-4">
202-
<thead>{renderTableHeader(false)}</thead>
203-
<tbody>
204-
{renderTableBody(otherStatusStudents, false)}
205-
</tbody>
206-
</table>
207-
</>
208-
)}
209183
</div>
210184
);
211185
};

frontend/src/Pages/ClassEnrollmentDetails.tsx

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export default function ClassEnrollmentDetails() {
4848
const [sortQuery, setSortQuery] = useState<string>(
4949
FilterResidentNames['Resident Name (A-Z)']
5050
);
51+
const [viewMode, setViewMode] = useState<'enrolled' | 'other'>('enrolled');
5152
const [filterStatus, setFilterStatus] = useState<string>('all');
5253
const [page, setPage] = useState(1);
5354
const [perPage, setPerPage] = useState(20);
@@ -58,19 +59,37 @@ export default function ClassEnrollmentDetails() {
5859
const confirmStateChangeModal = useRef<HTMLDialogElement>(null);
5960
const completionDetailsModal = useRef<HTMLDialogElement>(null);
6061
const reasonModalRef = useRef<HTMLDialogElement>(null);
61-
const [showOthers, setShowOthers] = useState(false);
6262
const requiresReason = (status?: string) =>
6363
[
6464
'incomplete: withdrawn',
6565
'incomplete: dropped',
6666
'incomplete: failed to complete'
6767
].includes(status?.toLowerCase() ?? '');
6868

69+
const getStatusParam = () => {
70+
if (viewMode === 'enrolled') {
71+
return 'enrolled';
72+
}
73+
if (filterStatus === 'all' || filterStatus === '') {
74+
return 'not_enrolled';
75+
}
76+
return filterStatus;
77+
};
78+
79+
const status = getStatusParam();
6980
const { data, error, isLoading, mutate } = useSWR<
7081
ServerResponseMany<ClassEnrollment>,
7182
Error
7283
>(
73-
`/api/program-classes/${class_id}/enrollments?search=${searchTerm}&page=${page}&per_page=${perPage}&order_by=${sortQuery}&status=${filterStatus}`
84+
`/api/program-classes/${class_id}/enrollments?search=${searchTerm}&page=${page}&per_page=${perPage}&order_by=${sortQuery}&status=${status}`
85+
);
86+
87+
const otherStatus = viewMode === 'enrolled' ? 'not_enrolled' : 'enrolled';
88+
const { data: otherData } = useSWR<
89+
ServerResponseMany<ClassEnrollment>,
90+
Error
91+
>(
92+
`/api/program-classes/${class_id}/enrollments?per_page=1&status=${otherStatus}`
7493
);
7594
if (error || redirect) {
7695
navigate(
@@ -85,6 +104,22 @@ export default function ClassEnrollmentDetails() {
85104
const enrollments = data?.data ?? [];
86105
const meta = data?.meta;
87106

107+
const enrolledCount =
108+
viewMode === 'enrolled'
109+
? meta?.total ?? 0
110+
: otherData?.meta?.total ?? 0;
111+
const otherCount =
112+
viewMode === 'enrolled'
113+
? otherData?.meta?.total ?? 0
114+
: meta?.total ?? 0;
115+
116+
const handleViewModeChange = (mode: 'enrolled' | 'other') => {
117+
setViewMode(mode);
118+
setPage(1);
119+
setSelectedResidents([]);
120+
setFilterStatus('all');
121+
};
122+
88123
const handleChange = (value: string, enrollment: ClassEnrollment) => {
89124
setSelectedResidents([]);
90125
setChangeStatusValue({
@@ -236,6 +271,28 @@ export default function ClassEnrollmentDetails() {
236271
<div className="flex flex-col gap-8">
237272
<div className="flex flex-row justify-between items-center">
238273
<div className="flex flex-row gap-2 items-center">
274+
<div className="inline-flex rounded-md border border-grey-2 h-12">
275+
<button
276+
onClick={() => handleViewModeChange('enrolled')}
277+
className={`px-4 text-sm font-medium transition-colors ${
278+
viewMode === 'enrolled'
279+
? 'bg-teal-3 text-white hover:bg-teal-4'
280+
: 'bg-transparent text-body-text hover:bg-teal-1'
281+
} rounded-l-md`}
282+
>
283+
Enrolled ({enrolledCount})
284+
</button>
285+
<button
286+
onClick={() => handleViewModeChange('other')}
287+
className={`px-4 text-sm font-medium transition-colors border-l border-grey-2 ${
288+
viewMode === 'other'
289+
? 'bg-teal-3 text-white hover:bg-teal-4'
290+
: 'bg-transparent text-body-text hover:bg-teal-1'
291+
} rounded-r-md`}
292+
>
293+
Graduated/Other ({otherCount})
294+
</button>
295+
</div>
239296
<SearchBar
240297
searchTerm={searchTerm}
241298
changeCallback={(term) => {
@@ -256,24 +313,25 @@ export default function ClassEnrollmentDetails() {
256313
'Enrollment Date (Desc)': 'start_dt desc'
257314
}}
258315
/>
259-
<DropdownControl
260-
customCallback={(value) => {
261-
setFilterStatus(value);
262-
setPage(1);
263-
}}
264-
enumType={{
265-
All: 'all',
266-
Enrolled: 'enrolled',
267-
Completed: 'completed',
268-
Withdrawn: 'incomplete: withdrawn',
269-
Dropped: 'incomplete: dropped',
270-
'Failed To Complete':
271-
'incomplete: failed to complete',
272-
Transferred: 'incomplete: transferred',
273-
Segregated: 'incomplete: segregated',
274-
Cancelled: 'incomplete: cancelled'
275-
}}
276-
/>
316+
{viewMode === 'other' && (
317+
<DropdownControl
318+
customCallback={(value) => {
319+
setFilterStatus(value);
320+
setPage(1);
321+
}}
322+
enumType={{
323+
All: 'all',
324+
Completed: 'completed',
325+
Withdrawn: 'incomplete: withdrawn',
326+
Dropped: 'incomplete: dropped',
327+
'Failed To Complete':
328+
'incomplete: failed to complete',
329+
Transferred: 'incomplete: transferred',
330+
Segregated: 'incomplete: segregated',
331+
Cancelled: 'cancelled'
332+
}}
333+
/>
334+
)}
277335
</div>
278336
<div
279337
className={`flex gap-2 ${blockEdits ? 'tooltip tooltip-left' : ''}`}
@@ -316,8 +374,7 @@ export default function ClassEnrollmentDetails() {
316374
isEditable={isEditable}
317375
handleChange={handleChange}
318376
handleShowCompletionDetails={handleShowCompletionDetails}
319-
showOthers={showOthers}
320-
setShowOthers={setShowOthers}
377+
viewMode={viewMode}
321378
classInfo={clsInfo}
322379
onEnrollmentUpdate={() => void mutate()}
323380
/>

0 commit comments

Comments
 (0)