Skip to content
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

23 new donated item form #33

Merged
merged 9 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion client-app/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import DonationForm from './Components/DonationForm';
import StatusDisplayPage from './Components/StatusDisplayPage';
import ProgramsPage from './Components/ProgramsPage';
import AddProgramPage from './Components/AddProgramPage'; // Import AddProgramPage correctly
import AddDonor from './Components/AddDonor';
import NewItemForm from './Components/NewItemForm.tsx';
import AddDonor from './Components/AddDonor'; // Why is this here?

function App() {
// Define handleAddProgram function here
Expand All @@ -39,6 +40,7 @@ function App() {
<Route path="/donation-form" element={<DonationForm />} />
<Route path="/programs" element={<ProgramsPage />} />
<Route path="/addprogram" element={<AddProgramPage />} />
<Route path="/adddonation" element={<NewItemForm />} />
<Route
path="/addprogram"
element={<AddProgramPage onAddProgram={handleAddProgram} />}
Expand Down
9 changes: 9 additions & 0 deletions client-app/src/Components/DonatedItemsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ function DonatedItemsList() {
navigate('/donation-form');
};

const handleAddNewDonationClick = () => {
// Navigate to NewItemForm page
navigate('/adddonation');
};

// Sample data for demonstration
const [donatedItems, setDonatedItems] = useState([
{
Expand Down Expand Up @@ -300,6 +305,10 @@ function DonatedItemsList() {
? 'Hide Assign Program'
: 'Assign Program'}
</button>

<button onClick={handleAddNewDonationClick}>
Add New Donation
</button>
</div>

<table className="item-list">
Expand Down
6 changes: 3 additions & 3 deletions client-app/src/Components/DonorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ const DonorForm: React.FC = () => {
)}
{renderFormField('State', 'state')}
{renderFormField('City', 'city')}
{renderFormField('Zip Code', 'zipcode')}
{renderFormField('Zip Code', 'zipcode')}*/

{/* Email Opt-In Field */}
<div className="form-field">
//{/* Email Opt-In Field */}
/*<div className="form-field">
<label
htmlFor="emailOptIn"
className="block text-sm font-semibold mb-1"
Expand Down
286 changes: 286 additions & 0 deletions client-app/src/Components/NewItemForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
import React, { useState, ChangeEvent, FormEvent } from 'react';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@truffer11 rename the file to DonatedItemForm.tsx

import axios from 'axios';
import '../css/DonorForm.css';

interface FormData {
itemType: string;
currentStatus: string;
donorEmail: string;
program: string;
imageUpload: string[];
dateDonated: string;
}

interface FormErrors {
[key: string]: string;
}

const NewItemForm: React.FC = () => {
const [formData, setFormData] = useState<FormData>({
itemType: '',
currentStatus: 'Received',
donorEmail: '',
program: '',
imageUpload: [],
dateDonated: '',
});

const itemTypeOptions = [
{value: 'bicycle', label: 'Bicycle'},
{value: 'computer', label: 'Computer'},
// More item type options can be added here
]

const donorEmailOptions = [
{value: 'email1', label: '[email protected]'},
{value: 'email2', label: '[email protected]'},
{value: 'email3', label: '[email protected]'},
]

const programOptions = [
{value: 'youthProgram', label: 'Youth Program'},
{value: 'retailSales', label: 'Retail Sales'},
{value: 'recycle', label: 'Recycle'},
{value: 'earnABicycle', label: 'Earn-a-bicycle'},
{value: 'earnAComputer', label: 'Earn-a-computer'},
]

const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = error => reject(error);
});
};

const [errors, setErrors] = useState<FormErrors>({});
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);

// Handle input change for all fields
const handleChange = async (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const {name, type, value, files} = e.target as HTMLInputElement;

if (name === 'imageUpload' && files) {
const fileArray = Array.from(files);
const base64Images = await Promise.all(fileArray.map(file=>convertToBase64(file)));
setFormData(prevState => ({
...prevState,
[name]: base64Images,
}));
} else {
setFormData(prevState => ({
...prevState,
[name] : value,
}));
}
setErrors(prevState => ({ ...prevState, [name]: '' })); // Reset errors on change
setErrorMessage(null);
setSuccessMessage(null);
};

// Generalized validation function to reduce code repetition
const validateField = (name: string, value: any) => {
const requiredFields = [
'itemType',
'currentStatus',
'donorEmail',
'program',
'imageUpload',
'dateDonated',
];

if (requiredFields.includes(name)) {
if (name === 'itemType') {
if (!value || value.length === 0) {
return "Please select an item type"
}
} else if (name === 'currentStatus') {
if (!value || value.length === 0) {
return "Please enter a status"
}
} else if (name === 'donorEmail') {
if (!value || value.length === 0) {
return "Please select the donor's email"
}
} else if (name === 'program') {
if (!value || value.length === 0) {
return "Please select a program"
}
} else if (name === 'imageUpload') {
if (!value || value.length === 0) {
return 'Please upload at least one image';
} else if (value.length > 5) {
return 'Please keep under 5 images'
}
} else if (name === 'dateDonated') {
if (!value || value.length === 0) {
return 'Please select a date'
}
} else if (typeof value === 'string' && !value.trim()) {
return `${name.replace(/([A-Z])/g, ' $1')} is required`;
}
}
return '';
};

// Validation for entire form
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;
};

// Handle form submission
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (validateForm()) {
try {
const response = await axios.post(
`${process.env.REACT_APP_BACKEND_API_BASE_URL}donatedItem`,
formData,
);
if (response.status === 201) {
setSuccessMessage('Item added successfully!');
console.log('Item Type: ' + formData.itemType);
console.log('Current Status ' + formData.currentStatus);
console.log('Donor Email: ' + formData.donorEmail);
console.log('Program: ' + formData.program);
console.log('Image Upload: ' + formData.imageUpload);
console.log('Date Donated: ' + formData.dateDonated);
setFormData({
itemType: '',
currentStatus: 'Received',
donorEmail: '',
program: '',
imageUpload: [],
dateDonated: '',
});
} else {
setErrorMessage('Item not added');
}
} catch (error: unknown) {
const message =
(error as any).response?.data?.message ||
'Error adding item';
setErrorMessage(message);
}
} else {
setErrorMessage('Form has validation errors');
}
};

// Handle form reset
const handleRefresh = () => {
setFormData({
itemType: '',
currentStatus: 'Received',
donorEmail: '',
program: '',
imageUpload: [],
dateDonated: '',
});
setErrors({});
setErrorMessage(null);
setSuccessMessage(null);
};

// Reusable function to render form fields (text, dropdown, date, and file upload)
const renderFormField = (
label: string,
name: keyof FormData,
type = 'text',
required = true,
options?: {value: string; label: string}[],
) => (
<div className="form-field">
<label htmlFor={name} className="block text-sm font-semibold mb-1">
{label}
{required && <span className="text-red-500">&nbsp;*</span>}
</label>
{type === 'file' ? (
<input
type = "file"
id = {name}
name = {name}
onChange = {handleChange}
multiple
accept = "image/*"
className = {`w-full px-3 py-2 rounded border ${errors[name] ? 'border-red-500' : 'border-gray-300'}`}
title = "Upload 1-5 images in JPG or PNG format"
/>
) : options ? ( // Only executes if there are options available
<select
id={name}
name={name}
value={formData[name]}
onChange={handleChange}
className={`w-full px-3 py-2 rounded border ${errors[name] ? 'border-red-500' : 'border-gray-300'}`}
>
<option value = "">Select {label}</option>
{options?.map(option => (
<option key = {option.value} value = {option.value}>
{option.label}
</option>
))}
</select>
) : (
<input
type={type}
id={name}
name={name}
value={formData[name] as string}
onChange={handleChange}
className={`w-full px-3 py-2 rounded border ${errors[name] ? 'border-red-500' : 'border-gray-300'}`}
disabled = {name === 'currentStatus'}
/>
)}
{errors[name] && (
<p className="text-red-500 text-sm mt-1">{errors[name]}</p>
)}
</div>
);

// HTML portion
return (
<div className="donor-form outer-container mx-auto p-10">
<h1 className="text-2xl font-bold heading-centered">
New Donated Item
</h1>
{errorMessage && <p className="error-message">{errorMessage}</p>}
{successMessage && (
<p className="success-message">{successMessage}</p>
)}
<form onSubmit={handleSubmit} className="form-grid">
{renderFormField('Item Type', 'itemType', 'text', true, itemTypeOptions)}
{renderFormField('Current Status', 'currentStatus')}
{renderFormField('Donor Email', 'donorEmail', 'text', true, donorEmailOptions)}
{renderFormField('Program', 'program', 'text', true, programOptions)}
{renderFormField('Date Donated', 'dateDonated', 'date')}
{renderFormField('Image Upload', 'imageUpload', 'file')}



<div className="form-field full-width button-container">
<button type="submit" className="submit-button">
Add Item
</button>
<button
type="button"
onClick={handleRefresh}
className="refresh-button"
>
Refresh
</button>
</div>
</form>
</div>
);
};

export default NewItemForm;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is same as donor form, this NewItemForm file should be imported in the home, or else both donor form and donated item from should be integrated. Separate routes should be created.

2 changes: 1 addition & 1 deletion client-app/src/css/DonorForm.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@
color: red;
margin-left: 2px;
font-weight: bold;
}
}
Loading