Skip to content

Commit

Permalink
feat: add apis integration to view excuses admin
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenyiCui committed Jun 10, 2024
1 parent 9712609 commit 91afda3
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 53 deletions.
73 changes: 26 additions & 47 deletions src/routes/admin/cohorts/ViewCohort.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactElement, useEffect, useReducer } from 'react';
import { ReactElement, useCallback, useEffect, useReducer } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Button, Flex, Stack, StackDivider, useToast } from '@chakra-ui/react';

Expand All @@ -16,10 +16,10 @@ import {
updateCohortAdmin,
updateWindowAdmin,
} from '@/lib/admin';
import { getExcuses } from '@/lib/excuses';
import { CohortAdminItem } from '@/types/api/admin';
import { ExcuseBase } from '@/types/api/excuses';
import { WindowBase } from '@/types/api/windows';
import { ExcuseFrom, ExcuseStatus } from '@/types/models/excuse';
import {
stripPrefixForUrlField,
stripSuffixForEmailField,
Expand All @@ -37,6 +37,7 @@ import { ViewCohortSkeleton } from './ViewCohortSkeleton';

interface State {
cohort: CohortAdminItem | null;
excuses: ExcuseBase[];
name: string;
coursemologyUrl: string;
email: string;
Expand All @@ -48,66 +49,39 @@ interface State {
selectedWindow: (Omit<WindowBase, 'id'> & { id: number | null }) | null;
}

const mockExcuses: ExcuseBase[] = [
{
id: 1,
user: {
name: 'Dev User',
githubUsername: 'straight-best-actor',
profileUrl: 'https://github.com',
photoUrl:
'https://res.cloudinary.com/folio-hnr/image/upload/v1679629122/blob_ycezgh.jpg',
},
window: {
id: 1,
startAt: new Date('2022-01-01'),
endAt: new Date('2022-01-07'),
numQuestions: 6,
requireInterview: true,
},
excuseFrom: ExcuseFrom.INTERVIEW_AND_QUESTION,
reason: 'I am sick',
status: ExcuseStatus.PENDING,
},
{
id: 2,
user: {
name: 'Shenyi Cui',
githubUsername: 'gay-best-actor',
profileUrl: 'https://github.com',
photoUrl: 'https://avatars.githubusercontent.com/u/29945147?v=4',
},
window: {
id: 2,
startAt: new Date('2022-01-08'),
endAt: new Date('2022-01-14'),
numQuestions: 6,
requireInterview: true,
},
excuseFrom: ExcuseFrom.QUESTION,
reason:
'I am going on a holiday really far away, this is a super long piece of text that should be truncated at some point in time',
status: ExcuseStatus.REJECTED,
},
];

export const ViewCohort = (): ReactElement<void, typeof ViewCohortPage> => {
const [state, setState] = useReducer(
(s: State, a: Partial<State>): State => ({ ...s, ...a }),
{
cohort: null,
excuses: [],
name: '',
coursemologyUrl: '',
email: '',
isError: false,
isUpdatingBasicInfo: false,
selectedWindow: null,
isUpdatingOrCreatingWindow: false,
isRematchingWindows: false,
isRematchWindowsModalShown: false,
} as State,
);
const toast = useToast();
const navigate = useNavigate();
const { id } = useParams();

const fetchExcuses = useCallback(async (): Promise<void> => {
if (id == null || id === undefined) {
return;
}
try {
const excuses = await getExcuses(+id);
setState({ excuses });
} catch {
toast(ERROR_TOAST_PROPS);
}
}, [id, toast]);

useEffect(() => {
let didCancel = false;
const fetchData = async (): Promise<void> => {
Expand All @@ -134,11 +108,12 @@ export const ViewCohort = (): ReactElement<void, typeof ViewCohortPage> => {
}
};
fetchData();
fetchExcuses();

return () => {
didCancel = true;
};
}, [id]);
}, [id, fetchExcuses]);

const { name, coursemologyUrl, email, cohort, isError, selectedWindow } =
state;
Expand Down Expand Up @@ -365,7 +340,11 @@ export const ViewCohort = (): ReactElement<void, typeof ViewCohortPage> => {
onView={(id: number): void => navigate(`${VIEW_WINDOW}/${id}`)}
windows={cohort.windows}
/>
<ExcuseTable excuses={mockExcuses} windows={cohort.windows} />
<ExcuseTable
excuses={state.excuses}
onDataUpdate={fetchExcuses}
windows={cohort.windows}
/>
</Stack>
<ConfirmRematchWindows
isLoading={state.isRematchingWindows}
Expand Down
66 changes: 63 additions & 3 deletions src/routes/admin/cohorts/tables/ExcuseTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import {
Stack,
Tag,
Text,
useToast,
} from '@chakra-ui/react';

import { Card } from '@/components/card';
import { Modal } from '@/components/modal';
import { Table } from '@/components/table';
import { UserProfile } from '@/components/userProfile';
import { DEFAULT_TOAST_PROPS, ERROR_TOAST_PROPS } from '@/constants/toast';
import { approveExcuse, rejectExcuse } from '@/lib/excuses';
import { ExcuseBase } from '@/types/api/excuses';
import { UserBase } from '@/types/api/users';
import { WindowBase } from '@/types/api/windows';
Expand Down Expand Up @@ -43,6 +46,7 @@ const getExcuseFromTags = (e: ExcuseFrom): string[] => {
interface Props {
excuses: ExcuseBase[];
windows: WindowBase[];
onDataUpdate: () => Promise<void>;
}

const getColumns = (
Expand Down Expand Up @@ -149,6 +153,7 @@ const getColumns = (
export const ExcuseTable = ({
excuses,
windows,
onDataUpdate,
}: Props): ReactElement<Props, typeof Card> => {
const [filter, setFilter] = useState('all');
const filteredExcuses = excuses.filter((excuse) => {
Expand All @@ -164,7 +169,9 @@ export const ExcuseTable = ({
}
});
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedExcuse, setSelectedExcuse] = useState<ExcuseBase | null>(null);
const toast = useToast();

const sortedWindows = useMemo(
() =>
Expand All @@ -190,9 +197,48 @@ export const ExcuseTable = ({
);

const onClose = (): void => {
onDataUpdate();
setIsOpen(false);
};

const onAccept = async (excuse: ExcuseBase): Promise<void> => {
try {
setIsLoading(true);
await approveExcuse(excuse.id);
await onDataUpdate();
toast({
...DEFAULT_TOAST_PROPS,
title: 'Success!',
status: 'success',
description: 'Excuse has been accepted successfully.',
});
onClose();
} catch {
toast(ERROR_TOAST_PROPS);
} finally {
setIsLoading(false);
}
};

const onReject = async (excuse: ExcuseBase): Promise<void> => {
try {
setIsLoading(true);
await rejectExcuse(excuse.id);
await onDataUpdate();
toast({
...DEFAULT_TOAST_PROPS,
title: 'Success!',
status: 'success',
description: 'Excuse has been rejected successfully.',
});
onClose();
} catch {
toast(ERROR_TOAST_PROPS);
} finally {
setIsLoading(false);
}
};

const columns = useMemo(
() => getColumns(onView, getWindowNumber),
[getWindowNumber, onView],
Expand All @@ -211,13 +257,27 @@ export const ExcuseTable = ({
<Modal
actions={
<HStack>
<Button onClick={onClose} variant="secondary">
<Button
isDisabled={isLoading}
onClick={onClose}
variant="secondary"
>
Close
</Button>
<Button colorScheme="red" variant="primary">
<Button
colorScheme="red"
isLoading={isLoading}
onClick={(): Promise<void> => onReject(selectedExcuse)}
variant="primary"
>
Reject
</Button>
<Button colorScheme="green" variant="primary">
<Button
colorScheme="green"
isLoading={isLoading}
onClick={(): Promise<void> => onAccept(selectedExcuse)}
variant="primary"
>
Accept
</Button>
</HStack>
Expand Down
10 changes: 7 additions & 3 deletions src/routes/tasks/breakdown/ExcuseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,22 @@ export const ExcuseModal = (
>
<form onSubmit={handleSubmit} ref={formRef}>
<Stack spacing={5}>
<FormControl isDisabled={!isEditable} isRequired={true}>
<FormControl isRequired={true}>
<FormLabel htmlFor="excuse-variant">Excuse From</FormLabel>
<CheckboxGroup
colorScheme="green"
defaultValue={mapExcuseFrom(excuse?.excuseFrom)}
>
<HStack spacing={10}>
<Checkbox name="excuseQuestion" value="QUESTION">
<Checkbox
isDisabled={!isEditable}
name="excuseQuestion"
value="QUESTION"
>
Questions
</Checkbox>
<Checkbox
isDisabled={!window.requireInterview}
isDisabled={!window.requireInterview || !isEditable}
name="excuseInterview"
value="INTERVIEW"
>
Expand Down

0 comments on commit 91afda3

Please sign in to comment.