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

Bug fixes #131

Closed
wants to merge 16 commits into from
Closed
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
77 changes: 68 additions & 9 deletions frontend/src/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Job = {
pay: number;
start: Date;
end: Date;
user_id: string;
};

interface Pet {
Expand Down Expand Up @@ -55,13 +56,25 @@ interface User {
username: string;
}

const PetCardChip = (props: { title: string; value: string }) => {
return (
<div className="flex flex-row border rounded-md truncate">
<span className="uppercase border-r-black font-light border-r-2 p-1 bg-slate-200 w-1/2 text-center">
{props.title}
</span>
<span className="flex flex-row items-center py-1 w-1/2 px-2">{props.value}</span>
</div>
);
};

const Dashboard = () => {
const [activeTab, setActiveTab] = useState("available jobs");
const [jobs, setJobs] = useState<Job[]>([]);
const [myApplications, setMyApplications] = useState<Application[]>([]);
const [searchTerm, setSearchTerm] = useState<string>("");
const [error, setError] = useState<string | null>(null);
// const [locations, setLocations] = useState<Location[]>([]);
const [petPictures, updatePetPictures] = useState<Record<string, string>>({});

useEffect(() => {
const fetchJobs = async () => {
Expand Down Expand Up @@ -98,6 +111,31 @@ const Dashboard = () => {
fetchJobs();
}, []);

useEffect(() => {
if (jobs.length) {
jobs.forEach(async (job: Job) => {
const petID = job.pet.id;
console.log(job);
axios
.get(`${API_ROUTES.USER.PET_PICTURE}?id=${petID}&owner_id=${job.location.user_id}`, {
responseType: "blob",
})
.then((response) => {
if (response.status === 200) {
const newPetPicture = URL.createObjectURL(response.data);
updatePetPictures((state) => ({
...state,
[petID]: newPetPicture,
}));
}
})
.catch((err) => {
console.error(`failed to fetch user pet picture with id: ${petID}`, err);
});
});
}
}, [jobs.length]);

const fetchMyApplications = async () => {
try {
const response = await axios.get(`${API_ROUTES.APPLY}`);
Expand Down Expand Up @@ -213,15 +251,36 @@ const Dashboard = () => {
<ul className="list-none p-0">
<li key={job.id} className="border border-gray-300 mb-4 p-4 rounded-md">
<div>
<p className="font-bold mb-2">Pet Name: {job.pet.name}</p>
<p>Job Status: {job.status}</p>
<p>
Location: {job?.location?.address ?? ""},{" "}
{job?.location?.city ?? ""}, {job?.location?.zipcode ?? ""}
</p>
<p>Pay: ${job.pay}</p>
<p>Start: {formatDate(job.start)}</p>
<p>End: {formatDate(job.end)}</p>
<h5 className="font-bold mb-2">Pet Name: {job.pet.name}</h5>
<div className="mb-3 font-normal text-gray-700 grid grid-cols-2 gap-2">
<PetCardChip title="Species" value={job.pet.species} />
<PetCardChip title="Breed" value={job.pet.breed} />
<PetCardChip title="Color" value={job.pet.color} />
<PetCardChip title="Height" value={job.pet.height} />
<PetCardChip title="Weight" value={job.pet.weight} />
<PetCardChip title="Chip" value={job.pet.chip_number} />
<p className="font-bold mb-2">
Health Requirements: {job.pet.health_requirements}
</p>
</div>
<hr />
<div className="flex flex-row justify-evenly items-center">
<div>
<p className="mt-4">Job Status: {job.status}</p>
<p>
Location: {job?.location?.address ?? ""},{" "}
{job?.location?.city ?? ""}, {job?.location?.zipcode ?? ""}
</p>
<p>Pay: ${job.pay}</p>
<p>Start: {formatDate(job.start)}</p>
<p>End: {formatDate(job.end)}</p>
</div>
<img
className="object-cover w-full mt-4 rounded-lg h-96 md:h-auto md:w-48 md:rounded-none md:rounded-s-lg"
src={petPictures[job.pet.id]}
alt={`${job.pet.name} picture`}
/>
</div>
{job.status === "open" && (
<button
onClick={() => applyForJob(job.id)}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const Home = (props: React.PropsWithChildren<HomeProps>) => {
} else if (pathname === ROUTES.PROTECTED_ROUTES.LOCATIONS) {
return <Locations />;
} else if (pathname === ROUTES.PROTECTED_ROUTES.PET_PROFILES) {
return <PetProfiles />;
return <PetProfiles userid={props.authContext.authenticationState.sessionInformation.id}/>;
} else if (pathname === ROUTES.PROTECTED_ROUTES.JOBS) {
return <JobPage />;
}
Expand Down
49 changes: 29 additions & 20 deletions frontend/src/Jobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ const Jobs: React.FC = () => {
Location: {job?.location?.address ?? ""}, {job?.location?.city ?? ""},{" "}
{job?.location?.zipcode ?? ""}
</p>
<p>Pay: {job.pay}</p>
<p>Pay: ${job.pay}</p>
<p>Start: {formatDate(job.start)}</p>
<p>End: {formatDate(job.end)}</p>
</div>
Expand Down Expand Up @@ -307,22 +307,30 @@ const JobPage: React.FC<JobPageProps> = () => {
}
};

const getDefaultLocation = (locations: Location[]) => {
const default_location = locations.filter((location: Location) => location.default_location);
if (default_location.length > 0) {
setJobFormData({ ...jobFormData, location: default_location[0].id });
}
};

const getLocations = () => {
return axios
.get(API_ROUTES.USER.LOCATION)
.then((response) => {
setLocations(response?.data ?? []);
const default_location = response.data.filter(
(location: Location) => location.default_location
);
if (default_location.length > 0) {
setJobFormData({ ...jobFormData, location: default_location[0].id });
}
})
.catch((err) => {
console.error("failed to fetch locations", err);
});
};

useEffect(() => {
if (jobFormData.location.length === 0) {
getDefaultLocation(locations);
}
}, [getLocations]);

const onClickSave = () => {
try {
checkDates();
Expand All @@ -332,7 +340,6 @@ const JobPage: React.FC<JobPageProps> = () => {
}
const saveConsent = window.confirm("Are you sure you want to make these changes?");
if (saveConsent) {
//console.log(jobFormData);
jobFormData.status = "open";
axios
.post(API_ROUTES.JOBS, jobFormData)
Expand Down Expand Up @@ -374,13 +381,8 @@ const JobPage: React.FC<JobPageProps> = () => {

const getCurrentDateTime = () => {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, "0");
const day = now.getDate().toString().padStart(2, "0");
const hour = now.getHours().toString().padStart(2, "0");
const minute = now.getMinutes().toString().padStart(2, "0");

return `${year}-${month}-${day}T${hour}:${minute}`;
const ISOString = now.toISOString();
return ISOString.slice(0, 16);
};

const handleStartUpdate = (e: any) => {
Expand All @@ -389,7 +391,7 @@ const JobPage: React.FC<JobPageProps> = () => {
const now = new Date();

if (selectedStart > now) {
setJobFormData({ ...jobFormData, start: e.target.value });
setJobFormData({ ...jobFormData, start: selectedStart.toISOString() });
} else {
setJobFormData({ ...jobFormData, start: "" });
toast.error("Start datetime must be in the future.");
Expand All @@ -403,7 +405,7 @@ const JobPage: React.FC<JobPageProps> = () => {
const selectedEndDate = new Date(e.target.value);

if (selectedEndDate > startDate) {
setJobFormData({ ...jobFormData, end: e.target.value });
setJobFormData({ ...jobFormData, end: selectedEndDate.toISOString() });
} else {
toast.error("End datetime must be after start.");
}
Expand All @@ -422,6 +424,12 @@ const JobPage: React.FC<JobPageProps> = () => {
}
};

const displayDate = (date: string) => {
const newDate = new Date(date);
newDate.setTime(newDate.getTime() - 5 * 60 * 60 * 1000);
return newDate.toISOString().slice(0, 16);
};

return (
<div className="max-w-screen-md mx-auto p-6">
<Tab.Group>
Expand Down Expand Up @@ -508,12 +516,13 @@ const JobPage: React.FC<JobPageProps> = () => {
<label htmlFor="job-start" className="block text-sm font-medium text-gray-700">
Start
</label>
<input type="hidden" id="timezone" name="timezone" value="-05:00" />
<input
type="datetime-local"
name="start"
min={getCurrentDateTime()}
id="job-start"
value={jobFormData.start}
value={jobFormData.start ? displayDate(jobFormData.start) : jobFormData.start}
onChange={(e) => handleStartUpdate(e)}
className="border border-gray-300 rounded-md p-2 mt-1"
/>
Expand All @@ -525,8 +534,8 @@ const JobPage: React.FC<JobPageProps> = () => {
type="datetime-local"
name="end"
id="job-end"
min={jobFormData.start}
value={jobFormData.end}
min={jobFormData.start ? displayDate(jobFormData.start) : getCurrentDateTime()}
value={jobFormData.end ? displayDate(jobFormData.end) : jobFormData.end}
onChange={(e) => handleEndUpdate(e)}
className="border border-gray-300 rounded-md p-2 mt-1"
/>
Expand Down
50 changes: 43 additions & 7 deletions frontend/src/Locations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ const Locations = () => {
setZipcode(location.zipcode);
};

const onClickDelete = (location: FurbabyLocation) => {
axios
.delete(API_ROUTES.USER.LOCATION, { data: { id: location.id } })
.then((resp) => {
console.log(resp);
getLocations();
})
.catch((err) => {
console.error(err);
});
};

const renderCards = React.useMemo(() => {
//console.log(locations);

Expand All @@ -113,18 +125,41 @@ const Locations = () => {
{loc.city}, {loc.country} - {loc.zipcode}
</p>
<div className="card-actions justify-between items-center mt-4">
<button
className="px-3 py-2 text-sm font-medium text-center text-white bg-blue-400 rounded-lg hover:bg-blue-600 focus:outline-none transition ease-in-out duration-150"
onClick={() => onClickEdit(loc)}
>
Edit
</button>
<div className="flex flex-row align-center space-x-4">
<button
className="px-3 py-2 text-sm font-medium text-center text-white bg-blue-400 rounded-lg hover:bg-blue-600 focus:outline-none transition ease-in-out duration-150"
onClick={() => onClickEdit(loc)}
>
Edit
</button>
<button
className="px-2 py-1.5 text-xs font-medium text-center text-white bg-red-300 rounded-lg hover:bg-red-400 focus:outline-none transition ease-in-out duration-150"
onClick={() => onClickDelete(loc)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244
2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5
0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
</div>
{loc.default_location ? (
<button
className="px-3 py-2 text-sm font-medium text-center text-white bg-red-300 rounded-lg hover:bg-red-400 focus:outline-none transition ease-in-out duration-150"
onClick={() => updateDefault(loc, false)}
>
Remove Default
Remove as Default
</button>
) : (
<button
Expand Down Expand Up @@ -165,6 +200,7 @@ const Locations = () => {
if (response.status === 200) {
onCloseModal();
toast.success("Location updated successfully.");
getLocations();
}
})
.catch((err) => {
Expand Down
13 changes: 7 additions & 6 deletions frontend/src/PetProfiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const PetCardChip = (props: { title: string; value: string }) => {
);
};

const PetProfiles: React.FC = () => {
const PetProfiles: React.FC = ({userid} : {userid : string}) => {
const [pets, setPets] = useState<Pet[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -68,7 +68,7 @@ const PetProfiles: React.FC = () => {
if (response.data.length) {
response.data.forEach((pet: Pet) => {
axios
.get(`${API_ROUTES.USER.PET_PICTURE}?id=${pet.id}`, {
.get(`${API_ROUTES.USER.PET_PICTURE}?id=${pet.id}&owner_id=${userid}`, {
responseType: "blob",
})
.then((response) => {
Expand Down Expand Up @@ -314,10 +314,10 @@ const PetProfiles: React.FC = () => {
alt={`${pet.name} picture`}
/>
<div className="flex flex-col justify-between p-4 leading-normal">
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900">
{pet.name}
</h5>
<div className="mb-3 font-normal text-gray-700 dark:text-gray-400 grid grid-cols-2 gap-2">
<div className="mb-3 font-normal text-gray-700 grid grid-cols-2 gap-2">
<PetCardChip title="Species" value={pet.species} />
<PetCardChip title="Breed" value={pet.breed} />
<PetCardChip title="Color" value={pet.color} />
Expand Down Expand Up @@ -358,7 +358,7 @@ const PetProfiles: React.FC = () => {
);
};

const PetProfilePage = () => {
const PetProfilePage = ({userid} : {userid : string}) => {
const [activeTab, setActiveTab] = useState("view");

const [petFormData, setPetFormData] = useState({
Expand Down Expand Up @@ -449,6 +449,7 @@ const PetProfilePage = () => {
}
};

// @ts-ignore
return (
<div className="max-w-screen-md mx-auto p-6">
<Tab.Group>
Expand All @@ -475,7 +476,7 @@ const PetProfilePage = () => {
</Tab>
</Tab.List>
<Tab.Panels className="p-4 bg-white border border-t-0 rounded-b-md">
<Tab.Panel>{activeTab === "view" && <PetProfiles />}</Tab.Panel>
<Tab.Panel>{activeTab === "view" && <PetProfiles userid={userid} /> }</Tab.Panel>
<Tab.Panel>
{activeTab === "add" && (
<div className="mb-4 flex flex-col justify-between">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const getCurrentAge = (date: string) => {
};

const formatDate = (date: Date) => {
return format(new Date(date), "MM/dd/yyyy HH:mm a");
return format(new Date(date), "MM/dd/yyyy hh:mm a");
};

export { classNames, formatDate, getCurrentAge, isJSONString, validateEmail };
2 changes: 2 additions & 0 deletions furbaby/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Meta:

class JobSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
start = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S%z") # type: ignore
end = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S%z") # type: ignore

class Meta:
model = Jobs
Expand Down
Loading
Loading