Skip to content

Commit

Permalink
Merge pull request #54 from CityOfDetroit/issue.52
Browse files Browse the repository at this point in the history
Use waste schedule API for calendar events
  • Loading branch information
jedgar1mx authored May 29, 2024
2 parents 3c9872a + 512b41d commit d9b46f1
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 144 deletions.
26 changes: 5 additions & 21 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,21 @@ import moment from 'moment';
import Panel from './Panel';
import Geocoder from './Geocoder';
import Cal from './Cal';
import { buildWasteAPI } from '../utils/WasteAPI';
import './App.scss';
import '../../node_modules/leaflet/dist/leaflet.css';

export default class App {
constructor() {
this.month = moment().month() + 1;
this.year = moment().year();
this.schedule = {
garbage: null,
recycle: null,
bulk: null,
yard: {
start: null,
end: null
}
}
this.point = null;
this.map = null;
this.layers = {};
this.calendar = new Cal('calendar', this);;
this.panel = new Panel(this);
this.geocoder = new Geocoder('geocoder', this);
this.routeNum = null;
this.initialLoad(this);
}

Expand Down Expand Up @@ -102,20 +95,11 @@ export default class App {
}
_app.map.flyTo(tempLocation, 15);
_app.panel.currentProvider = featureCollection.features[0].properties.contractor;
fetch(`https://apis.detroitmi.gov/waste_schedule/details/${featureCollection.features[0].properties.FID}/year/${_app.year}/month/${_app.month}/`)
_app.routeNum = featureCollection.features[0].properties.FID;
const wasteAPIEndpoint = buildWasteAPI(_app.routeNum, _app.year, _app.month);
fetch(wasteAPIEndpoint)
.then((res) => {
res.json().then(data => {
data.details.forEach((d)=>{
if(d.type == 'start-date' && d.service == 'yard waste'){
_app.schedule.yard.start = d.newDay;
}
if(d.type == 'end-date' && d.service == 'yard waste'){
_app.schedule.yard.end = d.newDay;
}
});
_app.schedule.garbage = data.next_pickups.trash.next_pickup;
_app.schedule.recycle = data.next_pickups.recycling.next_pickup;
_app.schedule.bulk = data.next_pickups.bulk.next_pickup;
_app.panel.location.lat = tempLocation.lat;
_app.panel.location.lng = tempLocation.lng;
_app.panel.data = data;
Expand Down
205 changes: 95 additions & 110 deletions src/components/Cal.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import moment from 'moment';
import { buildWasteAPI, PICKUP_TYPES, PICKUP_TYPES_PRINT } from '../utils/WasteAPI';
import './Cal.scss';
export default class Cal {
constructor(container, _controller) {
this.calendar = null;
this.controller = _controller;
this.pickups = [];
}

createCalendar(_app){
Expand All @@ -22,139 +22,124 @@ export default class Cal {
calContainer.id = 'calendar';
tempCal.innerHTML = `
<article class='cal-legend'>
<span class="garbage">Garbage</span>
<span class="garbage">Garbage</span>
<span class="recycle">Recycle</span>
<span class="bulk">Bulk</span>
<span class="yard">Yard</span>
</article>
`;
tempCal.appendChild(calContainer);
tempCal.prepend(closeBtn);
_app.calendar.buildSchedule(_app);
_app.calendar.calendar = new Calendar(calContainer, {
plugins: [ dayGridPlugin ],
eventSources: _app.calendar.pickups
showNonCurrentDates: false,
eventSources: [
{
events: this.fetchTrashPickups.bind(this),
color: '#cb4d4f',
textColor: 'white'
},
{
events: this.fetchRecyclingPickups.bind(this),
color: '#9FD5B3',
textColor: '#004445'
},
{
events: this.fetchBulkPickups.bind(this),
color: '#5f355a',
textColor: 'white'
},
{
events: this.fetchYardPickups.bind(this),
color: '#feb70d',
textColor: 'black'
}
],
});
_app.calendar.calendar.render();
document.querySelector('#app .calendar').className = "calendar active";
}

buildSchedule(_app){
_app.calendar.buildPickUps(_app);
fetchPickups(info, successCb, failureCb, pickupType, routeNum, eventBuilder) {
const month = info.start.getMonth() + 1;
const wasteAPIEndpoint = buildWasteAPI(routeNum, info.start.getFullYear(), month);
fetch(wasteAPIEndpoint, {'cache': 'force-cache'})
.then((res) => {
res.json().then((data) => {
const events = eventBuilder(data, pickupType);
successCb(events);
})
})
.catch((error) => {
console.error(error);
failureCb(error);
});
}

buildPickUps(_app){
let pastDate = _app.schedule.recycle;
let latestDate = _app.schedule.recycle;
let pastTrashDate = _app.schedule.garbage;
let trashDate = _app.schedule.garbage;
fetchTrashPickups(info, successCb, failureCb) {
this.fetchPickups(
info,
successCb,
failureCb,
PICKUP_TYPES.TRASH,
this.controller.routeNum,
this.buildPickUps
);
}

let gList = {
events: [
{
title : 'Trash',
start : moment(trashDate).format('YYYY-MM-DD'),
}
],
color: '#cb4d4f', // an option!
textColor: 'white' // an option!
};
let rList = {
events: [
{
title : 'Recycle',
start : moment(latestDate).format('YYYY-MM-DD'),
}
],
color: '#9FD5B3', // an option!
textColor: '#004445' // an option!
};
let bList = {
events: [
{
title : 'Bulk',
start : moment(latestDate).format('YYYY-MM-DD'),
}
],
color: '#5f355a', // an option!
textColor: 'white' // an option!
};
let yList = {
events: [],
color: '#feb70d', // an option!
textColor: 'black' // an option!
}
if(moment(latestDate).isBetween(_app.schedule.yard.start, _app.schedule.yard.end)){
yList.events.push(
{
title : 'Yard',
start : moment(latestDate).format('YYYY-MM-DD'),
}
);
}
for (let index = 0; index < 52; index++) {
let tempG = {
title : 'Trash',
start : moment(trashDate).add(7,'d').format('YYYY-MM-DD'),
};
let pasttempG = {
title : 'Trash',
start : moment(pastTrashDate).subtract(7,'d').format('YYYY-MM-DD'),
};
trashDate = moment(trashDate).add(7,'d');
pastTrashDate = moment(pastTrashDate).subtract(7,'d');
gList.events.push(tempG);
gList.events.push(pasttempG);
}
fetchRecyclingPickups(info, successCb, failureCb) {
this.fetchPickups(
info,
successCb,
failureCb,
PICKUP_TYPES.RECYCLING,
this.controller.routeNum,
this.buildPickUps
);
}

for (let index = 0; index < 26; index++) {
let tempR = {
title : 'Recycle',
start : moment(latestDate).add(14,'d').format('YYYY-MM-DD'),
};
let tempB = {
title : 'Bulk',
start : moment(latestDate).add(14,'d').format('YYYY-MM-DD'),
};
if(moment(latestDate).add(14,'d').isBetween(_app.schedule.yard.start, _app.schedule.yard.end)){
let tempY = {
title : 'Yard',
start : moment(latestDate).add(14,'d').format('YYYY-MM-DD'),
};
yList.events.push(tempY);
}
let pasttempR = {
title : 'Recycle',
start : moment(pastDate).subtract(14,'d').format('YYYY-MM-DD'),
};
let pasttempB = {
title : 'Bulk',
start : moment(pastDate).subtract(14,'d').format('YYYY-MM-DD'),
};
if(moment(pastDate).subtract(14,'d').isBetween(_app.schedule.yard.start, _app.schedule.yard.end)){
let pasttempY = {
title : 'Yard',
start : moment(pastDate).subtract(14,'d').format('YYYY-MM-DD'),
};
yList.events.push(pasttempY);
fetchYardPickups(info, successCb, failureCb) {
this.fetchPickups(
info,
successCb,
failureCb,
PICKUP_TYPES.YARD_WASTE,
this.controller.routeNum,
this.buildPickUps
);
}

fetchBulkPickups(info, successCb, failureCb) {
this.fetchPickups(
info,
successCb,
failureCb,
PICKUP_TYPES.BULK,
this.controller.routeNum,
this.buildPickUps
);
}

buildPickUps(eventData, pickupType){
let events = [];
eventData.schedule.forEach((pickupDateDetails) => {
for (const [pickupDate, pickupTypeDetails] of Object.entries(pickupDateDetails)) {
if (pickupType in pickupTypeDetails) {
events.push(
{
title : PICKUP_TYPES_PRINT[pickupType],
start : moment(pickupDate).format('YYYY-MM-DD'),
}
);
}
}
pastDate = moment(pastDate).subtract(14,'d');
latestDate = moment(latestDate).add(14,'d');
rList.events.push(tempR);
bList.events.push(tempB);
rList.events.push(pasttempR);
bList.events.push(pasttempB);
}
_app.calendar.pickups.push(gList);
_app.calendar.pickups.push(rList);
_app.calendar.pickups.push(bList);
_app.calendar.pickups.push(yList);
});
return events;
}

closeCalendar(ev,_calendar){
_calendar.calendar.destroy();
_calendar.calendar = null;
_calendar.pickups.length = 0;
let tempClass = ev.target.parentNode.parentNode.className;
tempClass = tempClass.split(' ');
ev.target.parentNode.parentNode.className = tempClass[0];
Expand Down
16 changes: 3 additions & 13 deletions src/components/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,8 @@ export default class Panel {
document.querySelector('#app .panel').className = "panel active";
}

checkRecyclingStatus(_panel){
if(_panel.data.next_pickups['yard waste']){
if(moment(_panel.data.next_pickups['yard waste'].next_pickup).isBetween(_panel.app.schedule.yard.start, _panel.app.schedule.yard.end)){
return true;
}else{
return false;
}
}else{
return false;
}
}

// TODO: Stop using next_pickups from API:
// https://github.com/CityOfDetroit/dpw-map/issues/53
buildNextPickup(_panel){
return `
Expand All @@ -155,7 +145,7 @@ export default class Panel {
<span class="header">BULK</span>
<p>${moment(_panel.data.next_pickups.bulk.next_pickup).format('dddd - MMM Do')}</p>
</div>
${(_panel.checkRecyclingStatus(_panel)) ? `
${('yard waste' in _panel.data.next_pickups) ? `
<div class="group">
<span class="header">YARD</span>
<p>${moment(_panel.data.next_pickups['yard waste'].next_pickup).format('dddd - MMM Do')}</p>
Expand Down
26 changes: 26 additions & 0 deletions src/utils/WasteAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Returns the API endpoint for the City of Detroit Waste Schedule API.
* @param {number} routeNum - The number denoting the waste pickup route.
* @param {string} year - The year formatted as YYYY.
* @param {string} month - The month formatted as MM and 1-indexed.
* @returns {string} - A REST API endpoint URL.
*/
function buildWasteAPI(routeNum, year, month) {
return `https://apis.detroitmi.gov/waste_schedule/details/${routeNum}/year/${year}/month/${month}/`;
}

const PICKUP_TYPES = Object.freeze({
TRASH: "trash",
RECYCLING: "recycling",
BULK: "bulk",
YARD_WASTE: "yard waste"
});

const PICKUP_TYPES_PRINT = Object.freeze({
"trash": "Garbage",
"recycling": "Recycle",
"bulk": "Bulk",
"yard waste": "Yard"
});

export {buildWasteAPI, PICKUP_TYPES, PICKUP_TYPES_PRINT};

0 comments on commit d9b46f1

Please sign in to comment.