Skip to content

Confirm Absence Modals and Toasts #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 16, 2025
Merged
32 changes: 25 additions & 7 deletions src/components/absences/details/AbsenceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { FiEdit2, FiMapPin, FiTrash2, FiUser } from 'react-icons/fi';
import { IoEyeOutline } from 'react-icons/io5';
import EditAbsenceForm from '../modals/edit/EditAbsenceForm';
import AbsenceFillThanks from './AbsenceFillThanks';
import ClaimAbsenceToast from './ClaimAbsenceToast';
import AbsenceStatusTag from './AbsenceStatusTag';
import EditableNotes from './EditableNotes';
import LessonPlanView from './LessonPlanView';
Expand Down Expand Up @@ -102,6 +103,13 @@ const AbsenceDetails: React.FC<AbsenceDetailsProps> = ({
const handleFillConfirm = async () => {
setIsFilling(true);

const formattedDate = new Date(event.start).toLocaleDateString('en-CA', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});

try {
const response = await fetch('/api/editAbsence', {
method: 'PUT',
Expand All @@ -126,21 +134,31 @@ const AbsenceDetails: React.FC<AbsenceDetailsProps> = ({
}

toast({
title: 'Absence filled',
description: 'You have successfully filled this absence.',
status: 'success',
isClosable: true,
position: 'bottom-left',
render: () => (
<ClaimAbsenceToast
firstName={event.absentTeacher.firstName}
date={formattedDate}
success={true}
/>
),
});

await fetchAbsences();
setIsFillDialogOpen(false);
setIsFillThanksOpen(true);
} catch (error) {
} catch {
toast({
title: 'Error',
description: error.message || 'Failed to fill absence',
status: 'error',
isClosable: true,
position: 'bottom-left',
render: () => (
<ClaimAbsenceToast
firstName={event.absentTeacher.firstName}
date={formattedDate}
success={false}
/>
),
});
} finally {
setIsFilling(false);
Expand Down
48 changes: 48 additions & 0 deletions src/components/absences/details/ClaimAbsenceToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Box, Text, useTheme } from '@chakra-ui/react';
import { MdCheckCircle, MdError } from 'react-icons/md';

const ClaimAbsenceToast = ({ firstName, date, success }) => {
const theme = useTheme();

const modalColor = success
? theme.colors.positiveGreen[200]
: theme.colors.errorRed[200];

const message = success
? `You have successfully claimed `
: `There was an error in claiming `;

const Icon = success ? MdCheckCircle : MdError;

return (
<Box
bg="white"
width="360px"
height="60px"
border="1px solid"
borderColor={modalColor}
borderRadius="md"
px={3}
py={3}
display="flex"
alignItems="center"
boxShadow="md"
>
<Box mr={4}>
<Icon size="38px" color={modalColor} />
</Box>
<Text fontSize="14px" color="black">
{message}
<Text as="span" fontWeight="bold">
{firstName}&apos;s
</Text>{' '}
absence on{' '}
<Text as="span" fontWeight="bold">
{date}.
</Text>
</Text>
</Box>
);
};

export default ClaimAbsenceToast;
109 changes: 88 additions & 21 deletions src/components/absences/modals/declare/ConfirmDeclareModal.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,120 @@
import { WarningTwoIcon } from '@chakra-ui/icons';
import {
Box,
Button,
HStack,
Icon,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useTheme,
VStack,
} from '@chakra-ui/react';

interface ConfirmAbsenceModalProps {
interface ConfirmDeclareModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
isSubmitting?: boolean;
isSubmitting: boolean;
lessonDate: string;
hasLessonPlan: boolean;
isWithin14Days: boolean;
}

export const ConfirmAbsenceModal: React.FC<ConfirmAbsenceModalProps> = ({
export const ConfirmDeclareModal: React.FC<ConfirmDeclareModalProps> = ({
isOpen,
onClose,
onConfirm,
isSubmitting = false,
isSubmitting,
lessonDate,
hasLessonPlan,
isWithin14Days,
}) => {
const formattedDate = new Date(lessonDate + 'T00:00:00').toLocaleDateString(
'en-CA',
{
weekday: 'long',
month: 'long',
day: 'numeric',
}
);
const theme = useTheme();

const formattedDate = new Date(lessonDate).toLocaleDateString('en-CA', {
month: 'long',
day: 'numeric',
year: 'numeric',
});

return (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader>Confirm Absence</ModalHeader>
<ModalBody>
<Text>
Please confirm your absence on <strong>{formattedDate}</strong>.
<ModalContent
maxW="300px"
height={isWithin14Days ? '200px' : '160px'}
borderRadius="lg"
>
<ModalHeader textAlign="center" fontSize="16px" pb={0} pt={5}>
<Text textStyle="h3">
{hasLessonPlan ? 'Confirm Absence' : 'No Lesson Plan Added'}
</Text>
</ModalHeader>
<ModalBody display="flex" justifyContent="center">
<Box maxW="224px" w="100%">
<VStack align="start" pl={1}>
<Text textStyle="subtitle" color="black">
{hasLessonPlan ? (
<>
Please confirm your absence on{' '}
<strong>{formattedDate}.</strong>
</>
) : (
<>
Declare absence on <strong>{formattedDate}</strong> without
adding a lesson plan?
</>
)}
</Text>
{isWithin14Days && (
<HStack align="center" spacing={3}>
<Icon
as={WarningTwoIcon}
boxSize="20px"
color={theme.colors.warningOrange[300]}
/>

<Text
textStyle="body"
color={theme.colors.warningOrange[300]}
>
You are submitting a late report. Please aim to report
absences at least 14 days in advance.
</Text>
</HStack>
)}
</VStack>
</Box>
</ModalBody>
<ModalFooter>
<Button onClick={onClose} mr={3}>
Cancel
<ModalFooter
display="flex"
justifyContent="center"
gap={5}
px={0}
pt={0}
>
<Button
onClick={onClose}
flex="1"
maxW="104px"
h="40px"
variant="outline"
>
Back
</Button>
<Button onClick={onConfirm} isLoading={isSubmitting}>
<Button
colorScheme="blue"
onClick={onConfirm}
isLoading={isSubmitting}
flex="1"
maxW="104px"
h="40px"
>
Confirm
</Button>
</ModalFooter>
Expand Down
12 changes: 9 additions & 3 deletions src/components/absences/modals/declare/DeclareAbsenceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { FileUpload } from '../../FileUpload';
import { AdminTeacherFields } from '../AdminTeacherFields';
import { DateOfAbsence } from '../DateOfAbsence';
import { InputDropdown } from '../InputDropdown';
import { ConfirmAbsenceModal } from './ConfirmDeclareModal';
import { ConfirmDeclareModal } from './ConfirmDeclareModal';

interface DeclareAbsenceFormProps {
onClose?: () => void;
Expand Down Expand Up @@ -170,6 +170,11 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
}));
};

const selectedDate = new Date(formData.lessonDate + 'T00:00:00');
const now = new Date();
const isWithin14Days =
(selectedDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) <= 14;

return (
<Box
as="form"
Expand Down Expand Up @@ -297,13 +302,14 @@ const DeclareAbsenceForm: React.FC<DeclareAbsenceFormProps> = ({
Declare Absence
</Button>
</VStack>

<ConfirmAbsenceModal
<ConfirmDeclareModal
isOpen={isOpen}
onClose={closeModal}
onConfirm={handleConfirmSubmit}
isSubmitting={isSubmitting}
lessonDate={formData.lessonDate}
hasLessonPlan={!!lessonPlan}
isWithin14Days={isWithin14Days}
/>
</Box>
);
Expand Down