Skip to content

Commit

Permalink
Fixed date/time bug and default location behavior bug (#129)
Browse files Browse the repository at this point in the history
1. Initially, the time of the job added by the owner and the job
displayed in the jobs list of the owner and the job feed of the sitter
were different due to a discrepancy in time zone. Now, the date and time
represented are of the same time zone.
2. Initially, when a job has been created and saved, the fields of the
create job form are emptied. However, we want the location to revert
back to the default location instead of empty. That has been fixed.
3. Added location delete functionality.

![Screenshot 2023-12-11
073606](https://github.com/gcivil-nyu-org/INET-Monday-Fall2023-Team-1/assets/71821708/bbf265b9-c508-4c53-88b4-a307a3104934)

---------

Co-authored-by: Sameer Kolhar <kolhar730@gmail.com>
  • Loading branch information
rajghodasara1 and kolharsam authored Dec 11, 2023
1 parent 8948ead commit e829a84
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 45 deletions.
78 changes: 69 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 All @@ -37,6 +38,7 @@ interface Location {
city: string;
country: string;
zipcode: string;
user_id: string;
}

interface Application {
Expand All @@ -55,13 +57,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 +112,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 +252,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
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
4 changes: 2 additions & 2 deletions frontend/src/PetProfiles.tsx
Original file line number Diff line number Diff line change
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
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

0 comments on commit e829a84

Please sign in to comment.