Skip to content

Commit

Permalink
add class detail page and student invitation to the class
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondhtet committed Sep 22, 2024
1 parent a042504 commit 9789255
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 163 deletions.
39 changes: 39 additions & 0 deletions app/(main)/classes/details/CreateInvitationComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import React, { Dispatch, SetStateAction } from 'react';
import { InputText } from 'primereact/inputtext';
import { ProgressSpinner } from 'primereact/progressspinner';

interface Props {
email: string;
setEmail: Dispatch<SetStateAction<string>>;
fullName: string;
setFullName: Dispatch<SetStateAction<string>>;
loading: boolean;
}

export default function CreateInvitationComponent(props: Props) {
return (
<div className="">
<div className="field grid">
<label htmlFor="email" className="col-12 mb-6 md:col-2 md:mb-0">
Email
</label>
<div className="col-12 md:col-10">
<InputText id="email" value={props.email} onChange={(e) => props.setEmail(e.target.value)} className="w-full" />
</div>
</div>

<div className="field grid">
<label htmlFor="fullName" className="col-12 mb-6 md:col-2 md:mb-0">
Full Name
</label>
<div className="col-12 md:col-10">
<InputText id="fullName" value={props.fullName} onChange={(e) => props.setFullName(e.target.value)} className="w-full" />
</div>
</div>

{props.loading && <ProgressSpinner style={{ position: 'absolute', top: '25%', left: '40%' }} />}
</div>
);
}
160 changes: 155 additions & 5 deletions app/(main)/classes/details/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,168 @@
'use client';

import { useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import AppMessages, { AppMessage } from '@/components/AppMessages';
import React, { useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { Panel } from 'primereact/panel';
import { UserService } from '@/service/UserService';
import moment from 'moment';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import CreateInvitationComponent from '@/app/(main)/classes/details/CreateInvitationComponent';
import { Dialog } from 'primereact/dialog';

const ClassDetails = () => {
const classId = useSearchParams().get('id');
const router = useRouter();
const classId = useSearchParams().get('id') || '';
const appMsg = useRef<AppMessage>(null);
const [classDetails, setClassDetails] = useState<ClassResponse>();
const [tutors, setTutors] = useState<Tutor[]>([]);
const [students, setStudents] = useState<ClassInvitation[]>([]);

const [email, setEmail] = useState<string>('');
const [fullName, setFullName] = useState<string>('');
const [inviteStudentStatus, setInviteStudentStatus] = useState(false);
const [loading, setLoading] = useState<boolean>(false);
const [showDialog, setShowDialog] = useState<boolean>(false);

useEffect(() => {
UserService.getClassById(classId).then((classResponse) => {
setClassDetails(classResponse);
setTutors(classResponse.tutors);

if (classResponse.classInvitations && classResponse.classInvitations.length > 0) {
setStudents(classResponse.classInvitations.map((v, i) => ({ userEmail: v.userEmail, status: v.status } as ClassInvitation)));
}
});
}, [inviteStudentStatus]);

const headerTemplate = (options: any) => {
const className = `${options.className} justify-content-space-between`;

return (
<div className={className}>
<div className="flex align-items-center gap-2">
<span className="font-bold">Students</span>
</div>
<div>
<Button label="Add" icon="pi pi-user-plus" onClick={() => setShowDialog(true)} text />
{options.togglerElement}
</div>
</div>
);
};

const clearInviteStudent = () => {
setShowDialog(false);
setEmail('');
setFullName('');
};

const inviteStudent = async () => {
setLoading(true);
try {
await UserService.sendInvitation({
studentEmail: email,
studentFullName: fullName,
classCode: classDetails!.code
});
invitationResponse(true);
} catch (e) {
invitationResponse(false);
} finally {
clearInviteStudent();
setLoading(false);
setInviteStudentStatus(true);
}
};

const invitationResponse = (isSucceeded: boolean) => {
if (!isSucceeded) appMsg.current?.showError(`Error invitation the student ${fullName} to the class ${classDetails?.description}. Please contact customer support for more info.`);
else appMsg.current?.showSuccess(`We have successfully invited ${fullName} to the class ${classDetails?.description}`);
};

const addStudentFooter = (
<div>
<Button label="Cancel" icon="pi pi-times" onClick={clearInviteStudent} className="p-button-text" />
<Button label="Save" icon="pi pi-save" onClick={inviteStudent} autoFocus disabled={loading} />
</div>
);

return (
<div className="grid">
<AppMessages ref={appMsg} isAutoDismiss={true} />
<AppMessages ref={appMsg} />

<Dialog header="Invite Student" style={{ width: '50vw' }} visible={showDialog} onHide={() => showDialog || clearInviteStudent()} footer={addStudentFooter}>
<CreateInvitationComponent setEmail={setEmail} email={email} setFullName={setFullName} fullName={fullName} loading={loading} />
</Dialog>

<Panel header="Class Details" className="col-12" toggleable>
<div className="grid">
<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">Code</label>
<p>{classDetails?.code}</p>
</div>
</div>
<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">Description</label>
<p>{classDetails?.description}</p>
</div>
</div>
<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">Subject</label>
<p>{classDetails?.subject}</p>
</div>
</div>
<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">Education Level</label>
<p>{classDetails?.educationLevel}</p>
</div>
</div>
<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">Start Date</label>
<p>{moment(classDetails?.startDate).format('DD MMM YYYY hh:mm a')}</p>
</div>
</div>
<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">End Date </label>
<p>{classDetails?.endDate && moment(classDetails?.endDate).format('DD MMM YYYY hh:mm a')}</p>
</div>
</div>

<div className="col-3">
<div className="text-left p-3 border-round-sm">
<label className="font-bold text-lg">Status</label>
<p>{classDetails?.status}</p>
</div>
</div>
</div>
</Panel>

<Panel header="Tutors" className="col-12" toggleable>
<DataTable value={tutors} tableStyle={{ minWidth: '20rem' }}>
<Column field="firstName" header="First Name" />
<Column field="lastName" header="Last Name" />
<Column field="email" header="Email" />
<Column field="educationLevel" header="Education Level" />
</DataTable>
</Panel>

<Panel header="Students" className="col-12" toggleable headerTemplate={headerTemplate}>
<DataTable value={students} tableStyle={{ minWidth: '20rem' }}>
<Column field="userEmail" header="Email" />
<Column field="status" header="Status" />
</DataTable>
</Panel>

<div className="col-12">
<div className="card">{classId}</div>
<Button label="Back" onClick={() => router.push('/classes')}></Button>
</div>
</div>
);
Expand Down
65 changes: 0 additions & 65 deletions app/(main)/students/invitation/CreateInvitationComponent.tsx

This file was deleted.

91 changes: 0 additions & 91 deletions app/(main)/students/page.tsx

This file was deleted.

3 changes: 1 addition & 2 deletions layout/AppMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ const AppMenu = () => {
to: '/questions/topics',
visible: accessibleBy(['admin', 'tutor'])
},
{ label: 'Classes', icon: 'pi pi-fw pi-sitemap', to: '/classes', visible: accessibleBy(['tutor']) },
{ label: 'Students', icon: 'pi pi-fw pi-users', to: '/students', visible: accessibleBy(['tutor']) }
{ label: 'Classes', icon: 'pi pi-fw pi-sitemap', to: '/classes', visible: accessibleBy(['tutor']) }
],
visible: accessibleBy(['admin', 'tutor'])
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"chart.js": "4.2.1",
"moment": "^2.30.1",
"next": "^14.2.3",
"primeflex": "^3.3.1",
"primeicons": "^7.0.0",
Expand Down
Loading

0 comments on commit 9789255

Please sign in to comment.