diff --git a/client-app/src/App.js b/client-app/src/App.js index 50836c74..4134d3dc 100644 --- a/client-app/src/App.js +++ b/client-app/src/App.js @@ -8,7 +8,7 @@ import ForgotPassword from './Components/ForgotPassword'; import ResetPasswordPage from './Components/ResetPasswordPage'; import DonatedItemsList from './Components/DonatedItemsList'; import DonorForm from './Components/DonorForm.tsx'; -import StatusDisplayPage from './Components/StatusDisplayPage'; +import StatusUpdate from './Components/AddNewStatus'; import Programs from './Components/Programs'; import AddProgramPage from './Components/AddProgramPage'; // Import AddProgramPage correctly import NewItemForm from './Components/NewItemForm.tsx'; @@ -35,7 +35,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/client-app/src/Components/AddNewStatus.tsx b/client-app/src/Components/AddNewStatus.tsx new file mode 100644 index 00000000..7a88f16d --- /dev/null +++ b/client-app/src/Components/AddNewStatus.tsx @@ -0,0 +1,205 @@ +import React, { useState, useEffect, ChangeEvent, FormEvent } from 'react'; +import axios from 'axios'; +import { useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import ItemStatus from '../constants/Enums'; +import '../css/DonorForm.css'; + +interface FormData { + statusType: string; + dateModified: string; + donatedItemId: string; +} + +interface FormErrors { + [key: string]: string; +} + +interface Option { + value: string; + label: string; + id?: number; +} + +const AddNewStatus: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [formData, setFormData] = useState({ + statusType: ItemStatus.DONATED, // Initial status + dateModified: '', + donatedItemId: id || '', + }); + const [errors, setErrors] = useState({}); + const [errorMessage, setErrorMessage] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + + useEffect(() => { // DOES NOT WORK. If time permits, this is trying to fetch the currentStatus of the item, then sets it to be the first thing displayed in the status box. + const fetchItemData = async () => { + try { + const response = await axios.get( + `${process.env.REACT_APP_BACKEND_API_BASE_URL}donatedItem/${id}` + ); + const data: FormData = response.data; + + setFormData({ + statusType: data.statusType || ItemStatus.DONATED, // Supposed to set the initial display to be the currentStatus + dateModified: '', + donatedItemId: id || '', + }); + } catch (error: any) { + setErrorMessage( + error.response?.data?.message || 'Error fetching item data' + ); + } + }; + + if (id) { + fetchItemData(); + } + }, [id]); // End of disfunctional code. Good luck if you are trying to get this working! + + const handleChange = async (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData(prevState => ({ + ...prevState, + [name]: value, + })); + setErrors(prevState => ({ ...prevState, [name]: '' })); + setErrorMessage(null); + setSuccessMessage(null); + }; + + const validateField = (name: string, value: any) => { + if (!value || value.length === 0) { + return `${name.replace(/([A-Z])/g, ' $1')} is required`; + } + return ''; + }; + + const validateForm = (): boolean => { + const newErrors: FormErrors = {}; + Object.keys(formData).forEach(field => { + const error = validateField(field, (formData as any)[field]); + if (error) newErrors[field] = error; + }); + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (event: FormEvent) => { + event.preventDefault(); + if (validateForm()) { + try { + const formDataToSubmit = new FormData(); + formDataToSubmit.append('statusType', formData.statusType); + formDataToSubmit.append('dateModified', formData.dateModified); + formDataToSubmit.append('donatedItemId', formData.donatedItemId); + + const response = await axios.post( + `${process.env.REACT_APP_BACKEND_API_BASE_URL}donatedItem/status/${id}`, + formDataToSubmit, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ); + + if (response.status === 200) { + setSuccessMessage('Item updated successfully!'); + handleRefresh(); + navigate(`/donations/${id}`); + } else { + setErrorMessage('Failed to update item'); + } + } catch (error: any) { + setErrorMessage( + error.response?.data?.message || 'Error updating item' + ); + } + } else { + setErrorMessage('Form has validation errors'); + } + }; + + + const handleRefresh = () => { + setFormData({ + statusType: 'Received', + dateModified: '', + donatedItemId: '', + }); + setErrors({}); + setErrorMessage(null); + setSuccessMessage(null); + }; + + const renderFormField = ( + label: string, + name: keyof FormData, + type = 'text', + required = true, + options?: Option[] + ) => ( +
+ + {options ? ( + + ) : ( + + )} + {errors[name] &&

{errors[name]}

} +
+ ); + + return ( +
+

Add New Status

+ {errorMessage &&

{errorMessage}

} + {successMessage &&

{successMessage}

} +
+ {renderFormField('Current Status', 'statusType', 'text', true, [ + { value: ItemStatus.DONATED, label: 'Donated' }, + { value: ItemStatus.IN_STORAGE, label: 'In storage facility' }, + { value: ItemStatus.REFURBISHED, label: 'Refurbished' }, + { value: ItemStatus.SOLD, label: 'Item sold' }, + { value: ItemStatus.RECEIVED, label: 'Received' }, + ])} + {renderFormField('Date Updated', 'dateModified', 'date')} + +
+ + +
+
+
+ ); +}; + +export default AddNewStatus; \ No newline at end of file diff --git a/client-app/src/Components/DonatedItemDetails.tsx b/client-app/src/Components/DonatedItemDetails.tsx index 1fe2ef64..86d214bd 100644 --- a/client-app/src/Components/DonatedItemDetails.tsx +++ b/client-app/src/Components/DonatedItemDetails.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; import axios from 'axios'; import { useParams } from 'react-router-dom'; import PersonIcon from '@mui/icons-material/Person'; @@ -16,6 +17,11 @@ const DonatedItemDetails: React.FC = () => { const [donatedItem, setDonatedItem] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); + const navigate = useNavigate(); + + const handleAddNewDonationClick = (): void => { + navigate(`/donatedItem/status/${id}`); + }; useEffect(() => { const fetchDonatedItemDetails = async () => { @@ -87,6 +93,9 @@ const DonatedItemDetails: React.FC = () => {

Donated Item Status

+
{donatedItem.statuses.map(status => (
diff --git a/client-app/src/Components/DonatedItemsList.tsx b/client-app/src/Components/DonatedItemsList.tsx index c548f0e8..488872ed 100644 --- a/client-app/src/Components/DonatedItemsList.tsx +++ b/client-app/src/Components/DonatedItemsList.tsx @@ -226,6 +226,7 @@ const DonatedItemsList: React.FC = () => { const handleAddNewDonationClick = (): void => { navigate('/adddonation'); }; + const downloadBarcode = (id: number) => { const barcodeElement = document.getElementById(`barcode-${id}`); if (barcodeElement) { diff --git a/client-app/src/Components/StatusDisplayPage.js b/client-app/src/Components/StatusDisplayPage.js deleted file mode 100644 index 155b6ccf..00000000 --- a/client-app/src/Components/StatusDisplayPage.js +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import ItemStatus from '../constants/Enums'; -import '../css/StatusDisplayPage.css'; - -const StatusDisplayPage = () => { - const location = useLocation(); - const itemInfo = location.state.itemInfo; - const navigate = useNavigate(); - - const [donorInfo, setDonorInfo] = useState({ - fullName: '', - email: '', - phone: '', - address: '', - status: { - donated: false, - inStorageFacility: false, - refurbished: false, - received: false, - sold: false, - }, - image: null, - }); - - // Simulating API call to fetch donor data - useEffect(() => { - // Replace with your actual API call logic - // For now, setting dummy data - const dummyData = { - fullName: 'Donor Full Name', - email: 'donor@example.com', - phone: '123-456-7890', - address: '123 Main St', - status: { - donated: true, - inStorageFacility: true, - refurbished: true, - received: true, - sold: true, - }, - }; - setDonorInfo(dummyData); - }, []); - - const handleCheckboxChange = e => { - const { name, checked } = e.target; - setDonorInfo({ - ...donorInfo, - status: { ...donorInfo.status, [name]: checked }, - }); - }; - - // Function to handle file upload - const handleImageUpload = e => { - const file = e.target.files[0]; - setDonorInfo({ - ...donorInfo, - image: URL.createObjectURL(file), // Generate a temporary URL for the selected image - }); - }; - const handleSaveChanges = () => { - // Logic to handle save changes - navigate('/Donations'); // Adjust '/donations' to your specific route - }; - - const handleCancel = () => { - navigate('/Donations'); // Adjust '/donations' to your specific route - }; - - if (!itemInfo) { - return
No item information found
; - } - - return ( -
-
- BWorks Logo - - -
- -
- -
- -
- -
- -
- -
- -
- -

- -

- {/* Display uploaded image */} - {donorInfo.image && ( - Uploaded - )} -
- -
- -
- -
-
- ); -}; - -export default StatusDisplayPage; diff --git a/client-app/src/css/DonatedItemDetails.css b/client-app/src/css/DonatedItemDetails.css index a21bb512..eec6c506 100644 --- a/client-app/src/css/DonatedItemDetails.css +++ b/client-app/src/css/DonatedItemDetails.css @@ -28,6 +28,16 @@ margin: 0; } +.section-header button { + margin-left: auto; /* Pushes the button to the right */ + padding: 5px 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + .section-header .icon { font-size: 28px; margin-right: 8px; diff --git a/client-app/src/css/StatusDisplayPage.css b/client-app/src/css/StatusDisplayPage.css deleted file mode 100644 index 4f692abf..00000000 --- a/client-app/src/css/StatusDisplayPage.css +++ /dev/null @@ -1,48 +0,0 @@ -/* BWorks logo styling */ -.bworks-logo { - display: block; - margin: 0 auto 20px; - max-width: 150px; - /* Adjust the size as needed */ -} - -/* Label styling */ -label { - display: block; - margin-bottom: 10px; - font-weight: bold; -} - -/* Input field styling */ -input[type='text'], -input[type='email'], -input[type='tel'] { - width: 100%; - padding: 10px; - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 15px; -} - -/* Radio button styling */ -input[type='radio'] { - margin-right: 10px; - vertical-align: middle; -} - -/* Buttons styling */ -button { - background-color: #0074d9; - color: #fff; - border: none; - padding: 10px 20px; - border-radius: 4px; - cursor: pointer; -} - -button[type='submit'] { - background-color: #2ecc40; -} - -/* Additional styling as needed */ -/* ... */ diff --git a/server/src/schemas/donatedItemSchema.ts b/server/src/schemas/donatedItemSchema.ts index b3c612e7..81a2f19f 100644 --- a/server/src/schemas/donatedItemSchema.ts +++ b/server/src/schemas/donatedItemSchema.ts @@ -1,9 +1,10 @@ import Joi from 'joi'; + //DonatedItem schema export const donatedItemSchema = Joi.object({ itemType: Joi.string().required(), currentStatus: Joi.string() - .valid('Received', 'Pending', 'Processed', 'Delivered') + .valid('Received', 'Donated', 'In storage facility', 'Refurbished', 'Item sold') .required(), donorId: Joi.alternatives(Joi.number(), Joi.string().pattern(/^\d+$/)) .required() @@ -24,7 +25,7 @@ export const donatedItemSchema = Joi.object({ // DonatedItemStatus schema export const donatedItemStatusSchema = Joi.object({ statusType: Joi.string() - .valid('Received', 'Pending', 'Processed', 'Delivered') + .valid('Received', 'Donated', 'In storage facility', 'Refurbished', 'Item sold') .required(), donatedItemId: Joi.number().integer().required(), // Ensures it's a valid integer dateModified: Joi.date(), // Validates as a proper date