Put flat details on main page
This commit is contained in:
parent
cc4c1ccb18
commit
c6f711030a
@ -16,7 +16,7 @@
|
|||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 75em;
|
/* max-width: 75em; */
|
||||||
font-family: "Helvetica", "Arial", sans-serif;
|
font-family: "Helvetica", "Arial", sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: 4em 1em;
|
padding: 4em 1em;
|
||||||
|
444
flatisfy/web/js_src/components/flat.vue
Normal file
444
flatisfy/web/js_src/components/flat.vue
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<template v-if="isLoading">
|
||||||
|
<p>{{ $t("common.loading") }}</p>
|
||||||
|
</template>
|
||||||
|
<div class="grid" v-else-if="flat && timeToPlaces">
|
||||||
|
<div class="left-panel">
|
||||||
|
<h2>
|
||||||
|
(<!--
|
||||||
|
--><router-link :to="{ name: 'status', params: { status: flat.status }}"><!--
|
||||||
|
-->{{ flat.status ? capitalize($t("status." + flat.status)) : '' }}<!--
|
||||||
|
--></router-link><!--
|
||||||
|
-->) {{ flat.title }} [{{ flat.id.split("@")[1] }}]
|
||||||
|
<span class="expired">{{ flat.is_expired ? '[' + $t('common.expired') + ']' : '' }}</span>
|
||||||
|
</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="left-panel">
|
||||||
|
<p>
|
||||||
|
{{ flat.cost | cost(flat.currency) }}
|
||||||
|
<template v-if="flat.utilities === 'included'">
|
||||||
|
{{ $t("flatsDetails.utilities_included") }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="flat.utilities === 'excluded'">
|
||||||
|
{{ $t("flatsDetails.utilities_excluded") }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="right-panel right">
|
||||||
|
{{ flat.area ? flat.area : '?' }} m<sup>2</sup>,
|
||||||
|
{{ flat.rooms ? flat.rooms : '?' }} {{ $tc("flatsDetails.rooms", flat.rooms) }} /
|
||||||
|
{{ flat.bedrooms ? flat.bedrooms : '?' }} {{ $tc("flatsDetails.bedrooms", flat.bedrooms) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<template v-if="flat.photos && flat.photos.length > 0">
|
||||||
|
<Slider :photos="flat.photos"></Slider>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>{{ $t("flatsDetails.Description") }}</h3>
|
||||||
|
<p>{{ flat.text }}</p>
|
||||||
|
<p class="right">{{ flat.location }}</p>
|
||||||
|
<p>{{ $t("flatsDetails.First_posted") }} {{ flat.date ? flat.date.fromNow() : '?' }}.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>{{ $t("flatsDetails.Details") }}</h3>
|
||||||
|
<table>
|
||||||
|
<tr v-for="(value, key) in flat.details">
|
||||||
|
<th>{{ key }}</th>
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>{{ $t("flatsDetails.Metadata") }}</h3>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{{ $t("flatsDetails.postal_code") }}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<template v-if="flat.flatisfy_postal_code.postal_code">
|
||||||
|
{{ flat.flatisfy_postal_code.name }} ({{ flat.flatisfy_postal_code.postal_code }})
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
?
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-if="displayedStations">
|
||||||
|
<th>
|
||||||
|
{{ $t("flatsDetails.nearby_stations") }}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
{{ displayedStations }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="Object.keys(flat.flatisfy_time_to).length">
|
||||||
|
<th>
|
||||||
|
{{ $t("flatsDetails.Times_to") }}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<ul class="time_to_list">
|
||||||
|
<li v-for="(time_to, place) in flat.flatisfy_time_to" :key="place">
|
||||||
|
{{ place }}: {{ humanizeTimeTo(time_to["time"]) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{{ $t("flatsDetails.SquareMeterCost") }}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
{{ flat.sqCost }} {{ flat.currency }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>{{ $t("flatsDetails.Location") }}</h3>
|
||||||
|
|
||||||
|
<FlatsMap :flats="flatMarkers" :places="timeToPlaces" :journeys="journeys"></FlatsMap>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>{{ $t("flatsDetails.Notes") }}</h3>
|
||||||
|
|
||||||
|
<form v-on:submit="updateFlatNotes">
|
||||||
|
<textarea ref="notesTextarea" rows="10" :v-model="flat.notes"></textarea>
|
||||||
|
<p class="right"><input type="submit" :value="$t('flatsDetails.Save')"/></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-panel">
|
||||||
|
<h3>{{ $t("flatsDetails.Contact") }}</h3>
|
||||||
|
<div class="contact">
|
||||||
|
<p>
|
||||||
|
<template v-if="flat.phone">
|
||||||
|
<template v-for="phoneNumber in flat.phone.split(',')">
|
||||||
|
<a :href="'tel:+33' + normalizePhoneNumber(phoneNumber)">{{ phoneNumber }}</a><br/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t("flatsDetails.no_phone_found") }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
<p>{{ $tc("common.Original_post", flat.urls.length) }}
|
||||||
|
<ul>
|
||||||
|
<li v-for="(url, index) in flat.urls">
|
||||||
|
<a :href="url" target="_blank">
|
||||||
|
{{ $tc("common.Original_post", 1) }} {{ index + 1 }}
|
||||||
|
<i class="fa fa-external-link" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>{{ $t("flatsDetails.Visit") }}</h3>
|
||||||
|
<div class="visit">
|
||||||
|
<flat-pickr
|
||||||
|
:value="flatpickrValue"
|
||||||
|
:config="flatpickrConfig"
|
||||||
|
:placeholder="$t('flatsDetails.setDateOfVisit')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>{{ $t("common.Actions") }}</h3>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<template v-if="flat.status !== 'user_deleted'">
|
||||||
|
<li ref="notationButton">
|
||||||
|
<template v-for="n in range(5)">
|
||||||
|
<button class="btnIcon" v-bind:key="n" v-on:mouseover="handleNotationHover(n)" v-on:mouseout="handleNotationOut()" v-on:click="updateFlatNotation(n)">
|
||||||
|
<i class="fa" v-bind:class="{'fa-star': n < notation, 'fa-star-o': n >= notation}" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button v-on:click="updateFlatStatus('user_deleted')" class="fullButton">
|
||||||
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
|
{{ $t("common.Remove") }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<li>
|
||||||
|
<button v-on:click="updateFlatStatus('new')" class="fullButton">
|
||||||
|
<i class="fa fa-undo" aria-hidden="true"></i>
|
||||||
|
{{ $t("common.Restore") }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import flatPickr from 'vue-flatpickr-component'
|
||||||
|
import moment from 'moment'
|
||||||
|
import 'font-awesome-webpack'
|
||||||
|
import 'flatpickr/dist/flatpickr.css'
|
||||||
|
|
||||||
|
import FlatsMap from '../components/flatsmap.vue'
|
||||||
|
import Slider from '../components/slider.vue'
|
||||||
|
|
||||||
|
import { capitalize, range } from '../tools'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FlatsMap,
|
||||||
|
Slider,
|
||||||
|
flatPickr
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
cost: function (value, currency) {
|
||||||
|
if (!value) {
|
||||||
|
return 'N/A'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currency === 'EUR') {
|
||||||
|
currency = ' €'
|
||||||
|
}
|
||||||
|
|
||||||
|
var valueStr = value.toString()
|
||||||
|
valueStr = ' '.repeat((3 + valueStr.length) % 3) + valueStr
|
||||||
|
|
||||||
|
return valueStr.match(/.{1,3}/g).join('.') + currency
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created () {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// TODO: Flatpickr locale
|
||||||
|
'overloadNotation': null,
|
||||||
|
'flatpickrConfig': {
|
||||||
|
static: true,
|
||||||
|
altFormat: 'h:i K, M j, Y',
|
||||||
|
altInput: true,
|
||||||
|
enableTime: true,
|
||||||
|
onChange: selectedDates => this.updateFlatVisitDate(selectedDates.length > 0 ? selectedDates[0] : null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
props: ['flat'],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isLoading () {
|
||||||
|
return this.$store.getters.isLoading
|
||||||
|
},
|
||||||
|
flatMarkers () {
|
||||||
|
return this.$store.getters.flatsMarkers(this.$router, flat => flat.id === this.flat.id)
|
||||||
|
},
|
||||||
|
'flatpickrValue' () {
|
||||||
|
if (this.flat && this.flat.visit_date) {
|
||||||
|
return this.flat.visit_date.local().format()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
timeToPlaces () {
|
||||||
|
return this.$store.getters.timeToPlaces(this.flat.flatisfy_constraint)
|
||||||
|
},
|
||||||
|
notation () {
|
||||||
|
if (this.overloadNotation) {
|
||||||
|
return this.overloadNotation
|
||||||
|
}
|
||||||
|
return this.flat.notation
|
||||||
|
},
|
||||||
|
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 () {
|
||||||
|
if (this.flat.flatisfy_stations.length > 0) {
|
||||||
|
const stationsNames = this.flat.flatisfy_stations.map(station => station.name)
|
||||||
|
return stationsNames.join(', ')
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
flat: 'fetchData'
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetchData () {
|
||||||
|
this.$store.dispatch('getAllTimeToPlaces')
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFlatNotation (notation) {
|
||||||
|
notation = notation + 1
|
||||||
|
|
||||||
|
if (notation === this.flat.notation) {
|
||||||
|
this.flat.notation = 0
|
||||||
|
this.$store.dispatch('updateFlatNotation', { flatId: this.flat.id, newNotation: 0 })
|
||||||
|
this.$store.dispatch('updateFlatStatus', { flatId: this.flat.id, newStatus: 'new' })
|
||||||
|
} else {
|
||||||
|
this.flat.notation = notation
|
||||||
|
this.$store.dispatch('updateFlatNotation', { flatId: this.flat.id, newNotation: notation })
|
||||||
|
this.$store.dispatch('updateFlatStatus', { flatId: this.flat.id, newStatus: 'followed' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFlatStatus (status) {
|
||||||
|
this.$store.dispatch('updateFlatStatus', { flatId: this.flat.id, newStatus: status })
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFlatNotes () {
|
||||||
|
const notes = this.$refs.notesTextarea.value
|
||||||
|
this.$store.dispatch(
|
||||||
|
'updateFlatNotes',
|
||||||
|
{ flatId: this.flat.id, newNotes: notes }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFlatVisitDate (date) {
|
||||||
|
if (date) {
|
||||||
|
date = moment(date).utc().format()
|
||||||
|
}
|
||||||
|
this.$store.dispatch(
|
||||||
|
'updateFlatVisitDate',
|
||||||
|
{ flatId: this.flat.id, newVisitDate: date }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
humanizeTimeTo (time) {
|
||||||
|
const minutes = Math.floor(time.as('minutes'))
|
||||||
|
return minutes + ' ' + this.$tc('common.mins', minutes)
|
||||||
|
},
|
||||||
|
|
||||||
|
handleNotationHover (n) {
|
||||||
|
this.overloadNotation = n + 1
|
||||||
|
},
|
||||||
|
|
||||||
|
handleNotationOut () {
|
||||||
|
this.overloadNotation = null
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizePhoneNumber (phoneNumber) {
|
||||||
|
phoneNumber = phoneNumber.replace(/ /g, '')
|
||||||
|
phoneNumber = phoneNumber.replace(/\./g, '')
|
||||||
|
return phoneNumber
|
||||||
|
},
|
||||||
|
|
||||||
|
capitalize: capitalize,
|
||||||
|
|
||||||
|
range: range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.expired {
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 50px;
|
||||||
|
grid-template-columns: 75fr 25fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel li {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 75%;
|
||||||
|
padding: 0.3em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time_to_list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style-position: outside;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnIcon {
|
||||||
|
border: none;
|
||||||
|
width: auto;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.right-panel nav {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullButton {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,11 +1,11 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="full">
|
<div class="full">
|
||||||
<v-map :zoom="zoom.defaultZoom" :center="center" :bounds="bounds" :min-zoom="zoom.minZoom" :max-zoom="zoom.maxZoom">
|
<v-map :zoom="zoom.defaultZoom" :center="center" :bounds="bounds" :min-zoom="zoom.minZoom" :max-zoom="zoom.maxZoom" v-on:click="$emit('select-flat', null)">
|
||||||
<v-tilelayer :url="tiles.url" :attribution="tiles.attribution"></v-tilelayer>
|
<v-tilelayer :url="tiles.url" :attribution="tiles.attribution"></v-tilelayer>
|
||||||
<v-marker-cluster>
|
<v-marker-cluster>
|
||||||
<template v-for="marker in flats">
|
<template v-for="marker in flats">
|
||||||
<v-marker :lat-lng="{ lat: marker.gps[0], lng: marker.gps[1] }" :icon="icons.flat">
|
<v-marker :lat-lng="{ lat: marker.gps[0], lng: marker.gps[1] }" :icon="icons.flat" v-on:click="$emit('select-flat', marker.flat_id)">
|
||||||
<v-popup :content="marker.content"></v-popup>
|
<!-- <v-popup :content="marker.content"></v-popup> -->
|
||||||
</v-marker>
|
</v-marker>
|
||||||
</template>
|
</template>
|
||||||
</v-marker-cluster>
|
</v-marker-cluster>
|
||||||
|
@ -38,15 +38,16 @@ export default {
|
|||||||
)
|
)
|
||||||
if (previousMarker) {
|
if (previousMarker) {
|
||||||
// randomize position a bit
|
// randomize position a bit
|
||||||
// gps[0] += (Math.random() - 0.5) / 500
|
gps[0] += (Math.random() - 0.5) / 500
|
||||||
// gps[1] += (Math.random() - 0.5) / 500
|
gps[1] += (Math.random() - 0.5) / 500
|
||||||
}
|
}
|
||||||
const href = router.resolve({ name: 'details', params: { id: flat.id }}).href
|
const href = router.resolve({ name: 'details', params: { id: flat.id }}).href
|
||||||
const cost = flat.cost ? ' ( ' + flat.cost + '€)' : ''
|
const cost = flat.cost ? ' ( ' + flat.cost + '€)' : ''
|
||||||
markers.push({
|
markers.push({
|
||||||
'title': '',
|
'title': '',
|
||||||
'content': '<a href="' + href + '">' + flat.title + '</a>' + cost,
|
'content': '<a href="' + href + '">' + flat.title + '</a>' + cost,
|
||||||
'gps': gps
|
'gps': gps,
|
||||||
|
'flat_id': flat.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,217 +3,18 @@
|
|||||||
<template v-if="isLoading">
|
<template v-if="isLoading">
|
||||||
<p>{{ $t("common.loading") }}</p>
|
<p>{{ $t("common.loading") }}</p>
|
||||||
</template>
|
</template>
|
||||||
<div class="grid" v-else-if="flat && timeToPlaces">
|
<Flat :flat="flat"></Flat>
|
||||||
<div class="left-panel">
|
|
||||||
<h2>
|
|
||||||
(<!--
|
|
||||||
--><router-link :to="{ name: 'status', params: { status: flat.status }}"><!--
|
|
||||||
-->{{ flat.status ? capitalize($t("status." + flat.status)) : '' }}<!--
|
|
||||||
--></router-link><!--
|
|
||||||
-->) {{ flat.title }} [{{ flat.id.split("@")[1] }}]
|
|
||||||
<span class="expired">{{ flat.is_expired ? '[' + $t('common.expired') + ']' : '' }}</span>
|
|
||||||
</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="left-panel">
|
|
||||||
<p>
|
|
||||||
{{ flat.cost }} {{ flat.currency }}
|
|
||||||
<template v-if="flat.utilities === 'included'">
|
|
||||||
{{ $t("flatsDetails.utilities_included") }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="flat.utilities === 'excluded'">
|
|
||||||
{{ $t("flatsDetails.utilities_excluded") }}
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p class="right-panel right">
|
|
||||||
{{ flat.area ? flat.area : '?' }} m<sup>2</sup>,
|
|
||||||
{{ flat.rooms ? flat.rooms : '?' }} {{ $tc("flatsDetails.rooms", flat.rooms) }} /
|
|
||||||
{{ flat.bedrooms ? flat.bedrooms : '?' }} {{ $tc("flatsDetails.bedrooms", flat.bedrooms) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<template v-if="flat.photos && flat.photos.length > 0">
|
|
||||||
<Slider :photos="flat.photos"></Slider>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>{{ $t("flatsDetails.Description") }}</h3>
|
|
||||||
<p>{{ flat.text }}</p>
|
|
||||||
<p class="right">{{ flat.location }}</p>
|
|
||||||
<p>{{ $t("flatsDetails.First_posted") }} {{ flat.date ? flat.date.fromNow() : '?' }}.</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>{{ $t("flatsDetails.Details") }}</h3>
|
|
||||||
<table>
|
|
||||||
<tr v-for="(value, key) in flat.details">
|
|
||||||
<th>{{ key }}</th>
|
|
||||||
<td>{{ value }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>{{ $t("flatsDetails.Metadata") }}</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
{{ $t("flatsDetails.postal_code") }}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<template v-if="flat.flatisfy_postal_code.postal_code">
|
|
||||||
{{ flat.flatisfy_postal_code.name }} ({{ flat.flatisfy_postal_code.postal_code }})
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
?
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
{{ $t("flatsDetails.nearby_stations") }}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<template v-if="displayedStations">
|
|
||||||
{{ displayedStations }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
?
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
{{ $t("flatsDetails.Times_to") }}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<template v-if="Object.keys(flat.flatisfy_time_to).length">
|
|
||||||
<ul class="time_to_list">
|
|
||||||
<li v-for="(time_to, place) in flat.flatisfy_time_to" :key="place">
|
|
||||||
{{ place }}: {{ humanizeTimeTo(time_to["time"]) }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
?
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
{{ $t("flatsDetails.SquareMeterCost") }}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
{{ flat.sqCost }} {{ flat.currency }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>{{ $t("flatsDetails.Location") }}</h3>
|
|
||||||
|
|
||||||
<FlatsMap :flats="flatMarkers" :places="timeToPlaces" :journeys="journeys"></FlatsMap>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>{{ $t("flatsDetails.Notes") }}</h3>
|
|
||||||
|
|
||||||
<form v-on:submit="updateFlatNotes">
|
|
||||||
<textarea ref="notesTextarea" rows="10" :v-model="flat.notes"></textarea>
|
|
||||||
<p class="right"><input type="submit" :value="$t('flatsDetails.Save')"/></p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-panel">
|
|
||||||
<h3>{{ $t("flatsDetails.Contact") }}</h3>
|
|
||||||
<div class="contact">
|
|
||||||
<p>
|
|
||||||
<template v-if="flat.phone">
|
|
||||||
<template v-for="phoneNumber in flat.phone.split(',')">
|
|
||||||
<a :href="'tel:+33' + normalizePhoneNumber(phoneNumber)">{{ phoneNumber }}</a><br/>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t("flatsDetails.no_phone_found") }}
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
<p>{{ $tc("common.Original_post", flat.urls.length) }}
|
|
||||||
<ul>
|
|
||||||
<li v-for="(url, index) in flat.urls">
|
|
||||||
<a :href="url" target="_blank">
|
|
||||||
{{ $tc("common.Original_post", 1) }} {{ index + 1 }}
|
|
||||||
<i class="fa fa-external-link" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>{{ $t("flatsDetails.Visit") }}</h3>
|
|
||||||
<div class="visit">
|
|
||||||
<flat-pickr
|
|
||||||
:value="flatpickrValue"
|
|
||||||
:config="flatpickrConfig"
|
|
||||||
:placeholder="$t('flatsDetails.setDateOfVisit')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>{{ $t("common.Actions") }}</h3>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<template v-if="flat.status !== 'user_deleted'">
|
|
||||||
<li ref="notationButton">
|
|
||||||
<template v-for="n in range(notation)">
|
|
||||||
<button class="btnIcon" v-on:mouseover="handleNotationHover(n)" v-on:mouseout="handleNotationOut()" v-on:click="updateFlatNotation(n)">
|
|
||||||
<i class="fa fa-star" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<template v-for="n in range(5 - notation)">
|
|
||||||
<button class="btnIcon" v-on:mouseover="handleNotationHover(notation + n)" v-on:mouseout="handleNotationOut()" v-on:click="updateFlatNotation(notation + n)">
|
|
||||||
<i class="fa fa-star-o" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button v-on:click="updateFlatStatus('user_deleted')" class="fullButton">
|
|
||||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
|
||||||
{{ $t("common.Remove") }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<li>
|
|
||||||
<button v-on:click="updateFlatStatus('new')" class="fullButton">
|
|
||||||
<i class="fa fa-undo" aria-hidden="true"></i>
|
|
||||||
{{ $t("common.Restore") }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import flatPickr from 'vue-flatpickr-component'
|
|
||||||
import moment from 'moment'
|
|
||||||
import 'font-awesome-webpack'
|
|
||||||
import 'flatpickr/dist/flatpickr.css'
|
|
||||||
|
|
||||||
import FlatsMap from '../components/flatsmap.vue'
|
import Flat from '../components/flat.vue'
|
||||||
import Slider from '../components/slider.vue'
|
|
||||||
|
|
||||||
import { capitalize, range } from '../tools'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
FlatsMap,
|
Flat
|
||||||
Slider,
|
|
||||||
flatPickr
|
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
created () {
|
||||||
document.title = this.title // Set title
|
document.title = this.title // Set title
|
||||||
|
|
||||||
@ -232,20 +33,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
// TODO: Flatpickr locale
|
|
||||||
'overloadNotation': null,
|
|
||||||
'flatpickrConfig': {
|
|
||||||
static: true,
|
|
||||||
altFormat: 'h:i K, M j, Y',
|
|
||||||
altInput: true,
|
|
||||||
enableTime: true,
|
|
||||||
onChange: selectedDates => this.updateFlatVisitDate(selectedDates.length > 0 ? selectedDates[0] : null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isLoading () {
|
isLoading () {
|
||||||
return this.$store.getters.isLoading
|
return this.$store.getters.isLoading
|
||||||
@ -253,204 +40,16 @@ export default {
|
|||||||
title () {
|
title () {
|
||||||
return 'Flatisfy - ' + this.$route.params.id
|
return 'Flatisfy - ' + this.$route.params.id
|
||||||
},
|
},
|
||||||
flatMarkers () {
|
|
||||||
return this.$store.getters.flatsMarkers(this.$router, flat => flat.id === this.$route.params.id)
|
|
||||||
},
|
|
||||||
flat () {
|
flat () {
|
||||||
return this.$store.getters.flat(this.$route.params.id)
|
return this.$store.getters.flat(this.$route.params.id)
|
||||||
},
|
|
||||||
'flatpickrValue' () {
|
|
||||||
if (this.flat && this.flat.visit_date) {
|
|
||||||
return this.flat.visit_date.local().format()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
timeToPlaces () {
|
|
||||||
return this.$store.getters.timeToPlaces(this.flat.flatisfy_constraint)
|
|
||||||
},
|
|
||||||
notation () {
|
|
||||||
if (this.overloadNotation) {
|
|
||||||
return this.overloadNotation
|
|
||||||
}
|
|
||||||
return this.flat.notation
|
|
||||||
},
|
|
||||||
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 () {
|
|
||||||
if (this.flat.flatisfy_stations.length > 0) {
|
|
||||||
const stationsNames = this.flat.flatisfy_stations.map(station => station.name)
|
|
||||||
return stationsNames.join(', ')
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetchData () {
|
fetchData () {
|
||||||
this.$store.dispatch('getFlat', { flatId: this.$route.params.id })
|
this.$store.dispatch('getFlat', { flatId: this.$route.params.id })
|
||||||
this.$store.dispatch('getAllTimeToPlaces')
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFlatNotation (notation) {
|
|
||||||
notation = notation + 1
|
|
||||||
|
|
||||||
if (notation === this.flat.notation) {
|
|
||||||
this.$store.dispatch('updateFlatNotation', { flatId: this.$route.params.id, newNotation: 0 })
|
|
||||||
this.$store.dispatch('updateFlatStatus', { flatId: this.$route.params.id, newStatus: 'new' })
|
|
||||||
} else {
|
|
||||||
this.$store.dispatch('updateFlatNotation', { flatId: this.$route.params.id, newNotation: notation })
|
|
||||||
this.$store.dispatch('updateFlatStatus', { flatId: this.$route.params.id, newStatus: 'followed' })
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
updateFlatStatus (status) {
|
|
||||||
this.$store.dispatch('updateFlatStatus', { flatId: this.$route.params.id, newStatus: status })
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFlatNotes () {
|
|
||||||
const notes = this.$refs.notesTextarea.value
|
|
||||||
this.$store.dispatch(
|
|
||||||
'updateFlatNotes',
|
|
||||||
{ flatId: this.$route.params.id, newNotes: notes }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFlatVisitDate (date) {
|
|
||||||
if (date) {
|
|
||||||
date = moment(date).utc().format()
|
|
||||||
}
|
|
||||||
this.$store.dispatch(
|
|
||||||
'updateFlatVisitDate',
|
|
||||||
{ flatId: this.$route.params.id, newVisitDate: date }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
humanizeTimeTo (time) {
|
|
||||||
const minutes = Math.floor(time.as('minutes'))
|
|
||||||
return minutes + ' ' + this.$tc('common.mins', minutes)
|
|
||||||
},
|
|
||||||
|
|
||||||
handleNotationHover (n) {
|
|
||||||
this.overloadNotation = n + 1
|
|
||||||
},
|
|
||||||
|
|
||||||
handleNotationOut () {
|
|
||||||
this.overloadNotation = null
|
|
||||||
},
|
|
||||||
|
|
||||||
normalizePhoneNumber (phoneNumber) {
|
|
||||||
phoneNumber = phoneNumber.replace(/ /g, '')
|
|
||||||
phoneNumber = phoneNumber.replace(/\./g, '')
|
|
||||||
return phoneNumber
|
|
||||||
},
|
|
||||||
|
|
||||||
capitalize: capitalize,
|
|
||||||
|
|
||||||
range: range
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.expired {
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 50px;
|
|
||||||
grid-template-columns: 75fr 25fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-panel {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-panel {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-panel textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact {
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-panel li {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 75%;
|
|
||||||
padding: 0.3em;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time_to_list {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
list-style-position: outside;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btnIcon {
|
|
||||||
border: none;
|
|
||||||
width: auto;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
|
||||||
.right-panel nav {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullButton {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="flex-row">
|
||||||
<FlatsMap :flats="flatsMarkers" :places="timeToPlaces"></FlatsMap>
|
<div class="flex" style="overflow: auto;">
|
||||||
|
<FlatsMap :flats="flatsMarkers" :places="timeToPlaces" v-on:select-flat="selectFlat($event)"></FlatsMap>
|
||||||
<h2>
|
<h2>
|
||||||
{{ $t("home.new_available_flats") }}
|
{{ $t("home.new_available_flats") }}
|
||||||
<template v-if="lastUpdate">
|
<template v-if="lastUpdate">
|
||||||
@ -33,17 +34,23 @@
|
|||||||
<p>{{ $t("flatListing.no_available_flats") }}</p>
|
<p>{{ $t("flatListing.no_available_flats") }}</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="selectedFlat" class="flex">
|
||||||
|
<Flat :flat="selectedFlat"></Flat>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FlatsMap from '../components/flatsmap.vue'
|
import FlatsMap from '../components/flatsmap.vue'
|
||||||
import FlatsTable from '../components/flatstable.vue'
|
import FlatsTable from '../components/flatstable.vue'
|
||||||
|
import Flat from '../components/flat.vue'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
FlatsMap,
|
FlatsMap,
|
||||||
FlatsTable
|
FlatsTable,
|
||||||
|
Flat
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
created () {
|
||||||
@ -59,7 +66,19 @@ export default {
|
|||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
showExpiredFlats: false
|
showExpiredFlats: false,
|
||||||
|
selectedFlat: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
selectFlat: async function (flatId) {
|
||||||
|
if (flatId) {
|
||||||
|
await this.$store.dispatch('getFlat', { flatId })
|
||||||
|
this.selectedFlat = await this.$store.getters.flat(flatId)
|
||||||
|
} else {
|
||||||
|
this.selectedFlat = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -99,7 +118,12 @@ h2 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
.flex-row {
|
||||||
|
display:flex;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
table {
|
table {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user