diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 36274b8d..921e5672 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -31,6 +31,7 @@ import { AuthenticatedUser } from "./types/AuthTypes"; import VolunteerViewEditUserProfilePage from "./features/user-profile/pages/VolunteerViewEditUserProfilePage"; import EditPetProfilePage from "./features/pet-profile/pages/EditPetProfilePage"; import EditTaskTemplatePage from "./features/task-management/pages/EditTaskTemplatePage"; +import AddPetForm from "./features/pet-profile/pages/AddPetForm"; const App = (): React.ReactElement => { const currentUser: AuthenticatedUser = getLocalStorageObj( @@ -102,6 +103,12 @@ const App = (): React.ReactElement => { component={EditPetProfilePage} allowedRoles={AuthConstants.ADMIN_AND_BEHAVIOURISTS} /> + { + const yearNum = + year === undefined || year === "" || year === "--" + ? undefined + : Number(year); + const daysInMonth = + month && month !== "--" ? getDaysInMonth(month, yearNum) : 31; + return [ + "--", + ...Array.from({ length: daysInMonth }, (_, i) => (i + 1).toString()), + ]; +}; + +const validateDate = (month: string, date: string, year: string) => { + const allUnset = [month, date, year].every( + (value) => value === "--" || value === "" || value === undefined, + ); + + if (allUnset) { + return true; + } + + const allSet = [month, date, year].every( + (value) => value !== "--" && value !== "" && value !== undefined, + ); + if (!allSet) { + return "Please complete all birthday fields or leave them all blank"; + } + + const daysInMonth = getDaysInMonth(month, Number(year)); + if (Number(date) > daysInMonth) { + return "Invalid date"; + } + return true; +}; + +const AddPetForm = (): React.ReactElement => { + const toast = useToast(); + const [localProfilePhoto, setLocalProfilePhoto] = useState< + string | undefined + >(undefined); + const [isUploading, setIsUploading] = useState(false); + const [page, setPage] = useState(1); + const { + isOpen: isQuitEditingModalOpen, + onOpen: openQuitEditingModal, + onClose: closeQuitEditingModal, + } = useDisclosure(); + const { + isOpen: isAddPetModalOpen, + onOpen: openAddPetModal, + onClose: closeAddPetModal, + } = useDisclosure(); + + // Birthday date options can vary depending on the month + const [birthdayDateOptions, setBirthdayDateOptions] = useState( + getBirthdayDateOptions("", ""), + ); + + const { + control, + watch, + trigger, + handleSubmit, + setValue, + getValues, + formState: { errors }, + } = useForm({ + defaultValues: { + name: "", + colourLevel: "", + animalTag: "", + breed: "", + weight: "", + birthdayMonth: "", + birthdayDate: "", + birthdayYear: "", + sex: "", + neutered: "", + safetyInfo: "", + managementInfo: "", + medicalInfo: "", + profilePhoto: "", + }, + mode: "onChange", // Errors are updated on every change + reValidateMode: "onChange", + }); + + // Dynamically update days when month/year changes + const selectedBirthdayMonth = watch("birthdayMonth"); + const selectedBirthdayYear = watch("birthdayYear"); + useEffect(() => { + setBirthdayDateOptions( + getBirthdayDateOptions(selectedBirthdayMonth, selectedBirthdayYear), + ); + }, [selectedBirthdayMonth, selectedBirthdayYear]); + + const onSubmit = async (data: FormData) => { + // Only allow a user to progress if they have resolved all errors + const isValid = await trigger([ + "name", + "colourLevel", + "animalTag", + "breed", + "weight", + "birthdayMonth", + "birthdayDate", + "birthdayYear", + "sex", + "neutered", + "safetyInfo", + "managementInfo", + "medicalInfo", + ]); + + if (isValid) { + // TODO: Remove this and submit actual data to backend endpoint + // eslint-disable-next-line no-console + console.log({ + name: data.name, + colourLevel: data.colourLevel, + animalTag: data.animalTag, + breed: data.breed, + weight: data.weight, + birthdayMonth: data.birthdayMonth, + birthdayDate: data.birthdayDate, + birthdayYear: data.birthdayYear, + sex: data.sex, + neutered: data.neutered, + safetyInfo: data.safetyInfo, + managementInfo: data.managementInfo, + medicalInfo: data.medicalInfo, + profilePhoto: localProfilePhoto, + }); + + openAddPetModal(); + } + }; + + const handleNextPage = async () => { + // Only allow a user to progress if they have resolved all errors + const isValid = await trigger([ + "name", + "colourLevel", + "animalTag", + "breed", + "weight", + ]); + + if (isValid) { + setPage(2); + } + }; + + const handleProfilePhotoChange = (files: FileList | null) => { + if (!files || files.length === 0) return; + const file = files[0]; + const reader = new FileReader(); + setIsUploading(true); + reader.onloadend = () => { + setLocalProfilePhoto(reader.result as string); + setValue("profilePhoto", reader.result as string, { + shouldValidate: true, + }); + setIsUploading(false); + }; + reader.readAsDataURL(file); + }; + + const colourLevelOptions = ["Green", "Yellow", "Orange", "Red", "Blue"]; // Assuming 1-5 levels + const colorLevelIcons = [ + , + , + , + , + , + ]; + const animalTagOptions = Object.values(AnimalTag); + const birthdayMonthOptions = [ + "--", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + // Birthday year options range from the current year to 1900 + const currentYear = new Date().getFullYear(); + const birthdayYearOptions = [ + "--", + ...Array.from({ length: currentYear - 1899 }, (_, i) => + (currentYear - i).toString(), + ), + ]; + const sexOptions = ["--", "Male", "Female"]; + const spayedNeuteredOptions = [ + "--", + "Neutered", + "Spayed", + "Unneutered", + "Unspayed", + ]; + + return ( + <> + + + + openQuitEditingModal()} + _hover={{ opacity: 0.7 }} + > + + + Back to Pet List + + + + + Create Pet Profile + + +
+ {page === 1 ? ( + // First page + <> + {/* Profile Photo */} + + + Profile Picture: + + + + + edit + + handleProfilePhotoChange(e.target.files)} + disabled={isUploading} + /> + + + + + {/* Name - Full width */} + ( + + )} + /> + + {/* Colour Level - Full width */} + ( + + )} + /> + + {/* Animal Tag - Full width */} + ( + + )} + /> + + {/* Breed, Weight - Side by side */} + + ( + + )} + /> + { + if (value === "" || value == null) return true; + return ( + /^\d*\.?\d+$/.test(value) || + "Weight has to be a number greater than 0" + ); + }, + }} + render={({ field }) => ( + + )} + /> + + + + ) : ( + // Second page + <> + + {/* Birthday Month (50%), Date (25%), Year (25%) */} + + + + { + const birthdayDate = getValues("birthdayDate"); + const birthdayYear = getValues("birthdayYear"); + return validateDate( + birthdayMonth, + birthdayDate, + birthdayYear, + ); + }, + }} + render={({ field }) => ( + { + if (value === "--") { + setValue("birthdayDate", "--"); + setValue("birthdayYear", "--"); + } + field.onChange(value); + }} + placeholder="Select month" + error={!!errors.birthdayMonth} + /> + )} + /> + + + ( + { + field.onChange(value); + // Re-evaulate month on date change + trigger("birthdayMonth"); + }} + placeholder="Select date" + error={!!errors.birthdayMonth} + /> + )} + /> + ( + { + field.onChange(value); + // Re-evaulate month on date change + trigger("birthdayMonth"); + }} + placeholder="Select year" + error={!!errors.birthdayMonth} + /> + )} + /> + + + {errors.birthdayMonth && ( + + {errors.birthdayMonth?.message} + + )} + + + {/* Sex, Neutered - Side by side */} + + ( + + )} + /> + ( + + )} + /> + + + {/* Safety info - Full width */} + ( +