Add a page listing flats by status
Also display the journeys on the details view.
This commit is contained in:
parent
5f2f4d0ccf
commit
0fb5f28184
@ -379,7 +379,7 @@ def compute_travel_times(flats_list, config):
|
|||||||
if time_to_place:
|
if time_to_place:
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"Travel time between %s and flat %s is %ds.",
|
"Travel time between %s and flat %s is %ds.",
|
||||||
place_name, flat["id"], time_to_place
|
place_name, flat["id"], time_to_place["time"]
|
||||||
)
|
)
|
||||||
flat["flatisfy"]["time_to"][place_name] = time_to_place
|
flat["flatisfy"]["time_to"][place_name] = time_to_place
|
||||||
return flats_list
|
return flats_list
|
||||||
|
@ -235,8 +235,8 @@ def get_travel_time_between(latlng_from, latlng_to, config):
|
|||||||
:param latlng_from: A tuple of (latitude, longitude) for the starting
|
:param latlng_from: A tuple of (latitude, longitude) for the starting
|
||||||
point.
|
point.
|
||||||
:param latlng_to: A tuple of (latitude, longitude) for the destination.
|
:param latlng_to: A tuple of (latitude, longitude) for the destination.
|
||||||
:return: The travel time in seconds. Returns ``None`` if it could not fetch
|
:return: A dict of the travel time in seconds and sections of the journey
|
||||||
it.
|
with GeoJSON paths. Returns ``None`` if it could not fetch it.
|
||||||
|
|
||||||
.. note :: Uses the Navitia API. Requires a ``navitia_api_key`` field to be
|
.. note :: Uses the Navitia API. Requires a ``navitia_api_key`` field to be
|
||||||
filled-in in the ``config``.
|
filled-in in the ``config``.
|
||||||
@ -258,7 +258,28 @@ def get_travel_time_between(latlng_from, latlng_to, config):
|
|||||||
auth=(config["navitia_api_key"], "")
|
auth=(config["navitia_api_key"], "")
|
||||||
)
|
)
|
||||||
req.raise_for_status()
|
req.raise_for_status()
|
||||||
time = req.json()["journeys"][0]["durations"]["total"]
|
|
||||||
|
journeys = req.json()["journeys"][0]
|
||||||
|
time = journeys["durations"]["total"]
|
||||||
|
sections = []
|
||||||
|
for section in journeys["sections"]:
|
||||||
|
if section["type"] == "public_transport":
|
||||||
|
# Public transport
|
||||||
|
sections.append({
|
||||||
|
"geojson": section["geojson"],
|
||||||
|
"color": (
|
||||||
|
section["display_informations"].get("color", None)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
elif section["type"] == "street_network":
|
||||||
|
# Walking
|
||||||
|
sections.append({
|
||||||
|
"geojson": section["geojson"],
|
||||||
|
"color": None
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Skip anything else
|
||||||
|
continue
|
||||||
except (requests.exceptions.RequestException,
|
except (requests.exceptions.RequestException,
|
||||||
ValueError, IndexError, KeyError) as exc:
|
ValueError, IndexError, KeyError) as exc:
|
||||||
# Ignore any possible exception
|
# Ignore any possible exception
|
||||||
@ -272,4 +293,10 @@ def get_travel_time_between(latlng_from, latlng_to, config):
|
|||||||
"No API key available for travel time lookup. Please provide "
|
"No API key available for travel time lookup. Please provide "
|
||||||
"a Navitia API key. Skipping travel time lookup."
|
"a Navitia API key. Skipping travel time lookup."
|
||||||
)
|
)
|
||||||
return time
|
if time:
|
||||||
|
return {
|
||||||
|
"time": time,
|
||||||
|
"sections": sections
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
@ -4,9 +4,8 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><router-link :to="{name: 'home'}">{{ $t("menu.available_flats") }}</router-link></li>
|
<li><router-link :to="{name: 'home'}">{{ $t("menu.available_flats") }}</router-link></li>
|
||||||
<li><router-link :to="{name: 'followed'}">{{ $t("menu.followed_flats") }}</router-link></li>
|
<li><router-link :to="{name: 'status', params: {status: 'followed'}}">{{ $t("menu.followed_flats") }}</router-link></li>
|
||||||
<li><router-link :to="{name: 'ignored'}">{{ $t("menu.ignored_flats") }}</router-link></li>
|
<li><router-link :to="{name: 'status', params: {status: 'new'}}">{{ $t("menu.by_status") }}</router-link></li>
|
||||||
<li><router-link :to="{name: 'user_deleted'}">{{ $t("menu.user_deleted_flats") }}</router-link></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
<v-tooltip :content="place_name"></v-tooltip>
|
<v-tooltip :content="place_name"></v-tooltip>
|
||||||
</v-marker>
|
</v-marker>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-for="journey in journeys">
|
||||||
|
<v-geojson-layer :geojson="journey.geojson" :options="Object.assign({}, defaultGeoJSONOptions, journey.options)"></v-geojson-layer>
|
||||||
|
</template>
|
||||||
</v-map>
|
</v-map>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -30,6 +33,13 @@ import Vue2Leaflet from 'vue2-leaflet'
|
|||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
defaultGeoJSONOptions: {
|
||||||
|
weight: 5,
|
||||||
|
color: '#000',
|
||||||
|
opacity: 1,
|
||||||
|
fillColor: '#e4ce7f',
|
||||||
|
fillOpacity: 1
|
||||||
|
},
|
||||||
center: null,
|
center: null,
|
||||||
zoom: {
|
zoom: {
|
||||||
defaultZoom: 13,
|
defaultZoom: 13,
|
||||||
@ -59,7 +69,8 @@ export default {
|
|||||||
'v-tilelayer': Vue2Leaflet.TileLayer,
|
'v-tilelayer': Vue2Leaflet.TileLayer,
|
||||||
'v-marker': Vue2Leaflet.Marker,
|
'v-marker': Vue2Leaflet.Marker,
|
||||||
'v-tooltip': Vue2Leaflet.Tooltip,
|
'v-tooltip': Vue2Leaflet.Tooltip,
|
||||||
'v-popup': Vue2Leaflet.Popup
|
'v-popup': Vue2Leaflet.Popup,
|
||||||
|
'v-geojson-layer': Vue2Leaflet.GeoJSON
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@ -77,7 +88,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
props: ['flats', 'places']
|
props: ['flats', 'places', 'journeys']
|
||||||
|
|
||||||
// TODO: Add a switch to display a layer with isochrones
|
// TODO: Add a switch to display a layer with isochrones
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,7 @@ export default {
|
|||||||
menu: {
|
menu: {
|
||||||
'available_flats': 'Available flats',
|
'available_flats': 'Available flats',
|
||||||
'followed_flats': 'Followed flats',
|
'followed_flats': 'Followed flats',
|
||||||
'ignored_flats': 'Ignored flats',
|
'by_status': 'Flats by status'
|
||||||
'user_deleted_flats': 'User deleted flats'
|
|
||||||
},
|
},
|
||||||
flatsDetails: {
|
flatsDetails: {
|
||||||
'Title': 'Title',
|
'Title': 'Title',
|
||||||
@ -49,7 +48,8 @@ export default {
|
|||||||
'new': 'new',
|
'new': 'new',
|
||||||
'followed': 'followed',
|
'followed': 'followed',
|
||||||
'ignored': 'ignored',
|
'ignored': 'ignored',
|
||||||
'user_deleted': 'user deleted'
|
'user_deleted': 'user deleted',
|
||||||
|
'duplicate': 'duplicate'
|
||||||
},
|
},
|
||||||
slider: {
|
slider: {
|
||||||
'Fullscreen_photo': 'Fullscreen photo'
|
'Fullscreen_photo': 'Fullscreen photo'
|
||||||
|
@ -11,9 +11,7 @@ export default new VueRouter({
|
|||||||
routes: [
|
routes: [
|
||||||
{ path: '/', component: Home, name: 'home' },
|
{ path: '/', component: Home, name: 'home' },
|
||||||
{ path: '/new', redirect: '/' },
|
{ path: '/new', redirect: '/' },
|
||||||
{ path: '/followed', component: Status, name: 'followed' },
|
{ path: '/status/:status', component: Status, name: 'status' },
|
||||||
{ path: '/ignored', component: Status, name: 'ignored' },
|
|
||||||
{ path: '/user_deleted', component: Status, name: 'user_deleted' },
|
|
||||||
{ path: '/flat/:id', component: Details, name: 'details' }
|
{ path: '/flat/:id', component: Details, name: 'details' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
<template v-if="Object.keys(flat.flatisfy_time_to).length">
|
<template v-if="Object.keys(flat.flatisfy_time_to).length">
|
||||||
<ul class="time_to_list">
|
<ul class="time_to_list">
|
||||||
<li v-for="(time_to, place) in flat.flatisfy_time_to" :key="place">
|
<li v-for="(time_to, place) in flat.flatisfy_time_to" :key="place">
|
||||||
{{ place }}: {{ time_to }}
|
{{ place }}: {{ time_to["time"] }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
@ -98,7 +98,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h3>{{ $t("flatsDetails.Location") }}</h3>
|
<h3>{{ $t("flatsDetails.Location") }}</h3>
|
||||||
|
|
||||||
<FlatsMap :flats="flatMarkers" :places="timeToPlaces"></FlatsMap>
|
<FlatsMap :flats="flatMarkers" :places="timeToPlaces" :journeys="journeys"></FlatsMap>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
@ -195,6 +195,24 @@ export default {
|
|||||||
flat () {
|
flat () {
|
||||||
return this.$store.getters.flat(this.$route.params.id)
|
return this.$store.getters.flat(this.$route.params.id)
|
||||||
},
|
},
|
||||||
|
journeys () {
|
||||||
|
if (Object.keys(this.flat.flatisfy_time_to).length > 0) {
|
||||||
|
const journeys = []
|
||||||
|
for (const place in this.flat.flatisfy_time_to) {
|
||||||
|
this.flat.flatisfy_time_to[place].sections.forEach(
|
||||||
|
section => journeys.push({
|
||||||
|
geojson: section.geojson,
|
||||||
|
options: {
|
||||||
|
color: section.color ? ('#' + section.color) : '#2196f3',
|
||||||
|
dashArray: section.color ? 'none' : '2, 10'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return journeys
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
displayedStations () {
|
displayedStations () {
|
||||||
if (this.flat.flatisfy_stations.length > 0) {
|
if (this.flat.flatisfy_stations.length > 0) {
|
||||||
const stationsNames = this.flat.flatisfy_stations.map(station => station.name)
|
const stationsNames = this.flat.flatisfy_stations.map(station => station.name)
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2>{{ capitalize($t("status." + $route.name)) }}</h2>
|
<h2 class="btn-group">
|
||||||
|
<button type="button" class="dropdownToggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-on:click="toggleDropdown()" ref="dropdownToggle">
|
||||||
|
<span class="dashedUnderline">{{ capitalize($t("status." + $route.params.status)) }}</span>
|
||||||
|
<i class="fa fa-caret-down"/>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdownMenu" :class="isDropdownVisible ? '' : 'hidden'">
|
||||||
|
<li v-for="status in available_status" :key="status" :class="$route.params.status == status.toLowerCase() ? 'active' : ''">
|
||||||
|
<router-link :to="{ name: 'status', params: {status: status.toLowerCase()}}" role="button">
|
||||||
|
{{ capitalize($t("status." + status)) }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</h2>
|
||||||
<template v-if="Object.keys(postalCodesFlatsBuckets).length">
|
<template v-if="Object.keys(postalCodesFlatsBuckets).length">
|
||||||
<template v-for="(postal_code_data, postal_code) in postalCodesFlatsBuckets">
|
<template v-for="(postal_code_data, postal_code) in postalCodesFlatsBuckets">
|
||||||
<h3>{{ postal_code_data.name }} ({{ postal_code }}) - {{ postal_code_data.flats.length }} {{ $tc("common.flats", 42) }}</h3>
|
<h3>{{ postal_code_data.name }} ({{ postal_code }}) - {{ postal_code_data.flats.length }} {{ $tc("common.flats", 42) }}</h3>
|
||||||
@ -19,6 +31,19 @@ import { capitalize } from '../tools'
|
|||||||
import FlatsTable from '../components/flatstable.vue'
|
import FlatsTable from '../components/flatstable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
'isDropdownVisible': false,
|
||||||
|
'available_status': [
|
||||||
|
'new',
|
||||||
|
'followed',
|
||||||
|
'ignored',
|
||||||
|
'duplicate',
|
||||||
|
'user_deleted'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
FlatsTable
|
FlatsTable
|
||||||
},
|
},
|
||||||
@ -28,14 +53,114 @@ export default {
|
|||||||
this.$store.dispatch('getAllFlats')
|
this.$store.dispatch('getAllFlats')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
window.addEventListener('click', event => {
|
||||||
|
if (event.target !== this.$refs.dropdownToggle) {
|
||||||
|
this.isDropdownVisible = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
postalCodesFlatsBuckets () {
|
postalCodesFlatsBuckets () {
|
||||||
return this.$store.getters.postalCodesFlatsBuckets(flat => flat.status === this.$route.name)
|
return this.$store.getters.postalCodesFlatsBuckets(flat => flat.status === this.$route.params.status)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleDropdown () {
|
||||||
|
this.isDropdownVisible = !this.isDropdownVisible
|
||||||
|
},
|
||||||
capitalize: capitalize
|
capitalize: capitalize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.btn-group {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group > .btn {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group > .btn:hover {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group > .btn:focus,
|
||||||
|
.btn-group > .btn:active,
|
||||||
|
.btn-group > .btn.active {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownToggle {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1em;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 21px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
font-family: "Helvetica", "Arial", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownMenu {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
min-width: 160px;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1;
|
||||||
|
padding-left: 0;
|
||||||
|
border-radius: .25rem;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownMenu li {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 1em;
|
||||||
|
border-radius: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownMenu a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashedUnderline {
|
||||||
|
border-bottom: 1px dotted black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #0275d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownMenu li.active a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-caret-down {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-left: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashedUnderline,
|
||||||
|
.fa-caret-down {
|
||||||
|
/* Fix for alignment of caret and border-bottom */
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user