Browse Source

Better UI

Phyks (Lucas Verney) 3 years ago
parent
commit
adccc4b220

+ 2
- 0
build/webpack.base.conf.js View File

@@ -35,6 +35,8 @@ module.exports = {
35 35
     extensions: ['.js', '.vue', '.json'],
36 36
     alias: {
37 37
       'vue$': 'vue/dist/vue.esm.js',
38
+      'masonry': 'masonry-layout',
39
+      'isotope': 'isotope-layout',
38 40
       '@': resolve('src'),
39 41
     }
40 42
   },

+ 2
- 0
package.json View File

@@ -20,7 +20,9 @@
20 20
     "moment": "^2.19.3",
21 21
     "vue": "^2.5.2",
22 22
     "vue-i18n": "^7.3.2",
23
+    "vue-images-loaded": "^1.1.2",
23 24
     "vue-router": "^3.0.1",
25
+    "vueisotope": "^3.1.0",
24 26
     "vuetify": "^0.17.4",
25 27
     "vuex": "^3.0.1"
26 28
   },

+ 11
- 1
src/App.vue View File

@@ -24,7 +24,9 @@
24 24
         </v-navigation-drawer>
25 25
         <v-toolbar fixed app clipped-left>
26 26
             <v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
27
-            <v-toolbar-title>Flatisfy</v-toolbar-title>
27
+            <v-toolbar-title>
28
+                <router-link :to="{ name: 'Home' }">Flatisfy</router-link>
29
+            </v-toolbar-title>
28 30
             <v-spacer></v-spacer>
29 31
         </v-toolbar>
30 32
         <v-content>
@@ -59,4 +61,12 @@ export default {
59 61
 .panel {
60 62
     max-width: 960px;
61 63
 }
64
+
65
+.flex-icon {
66
+    flex: 0 0;
67
+}
68
+
69
+.fa-icon {
70
+    font-size: 20px;
71
+}
62 72
 </style>

+ 15
- 2
src/api/index.js View File

@@ -41,8 +41,21 @@ function _postProcessAPIResults(flatFromAPI) {
41 41
     /* eslint-enable camelcase */
42 42
 }
43 43
 
44
-function getFlats() {
45
-    return fetch(`${CONSTANTS.BASE_API_URL}/api/v1/flats`)
44
+function getFlats(pageNumber = 0, pageSize = CONSTANTS.ITEMS_PER_PAGE) {
45
+    // Handle pagination GET params
46
+    const params = {};
47
+    if (pageNumber) {
48
+        params['page[number]'] = pageNumber;
49
+    }
50
+    if (pageSize) {
51
+        params['page[size]'] = pageSize;
52
+    }
53
+    const query = Object.keys(params).map(
54
+        k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`,
55
+    ).join('&');
56
+
57
+    // Make API call
58
+    return fetch(`${CONSTANTS.BASE_API_URL}/api/v1/flats?${query}`, params)
46 59
         .then(response => response.json())
47 60
         .then(json => json.data.map(_postProcessAPIResults))
48 61
         .catch((ex) => {

+ 35
- 2
src/components/FlatDetails.vue View File

@@ -1,11 +1,41 @@
1 1
 <template>
2
-    <div v-if="!isLoading">
3
-        <p>{{ flat.id }}</p>
2
+    <div>
3
+        <template v-if="isLoading"></template>
4
+        <template v-else>
5
+            <template v-if="flatIsNotEmpty">
6
+                <h2>{{ flat.title }}</h2>
7
+                <FormattedCost :cost="flat.cost" :currency="flat.currency"></FormattedCost>
8
+                <FormattedLocation :location="flat.location"></FormattedLocation>
9
+                <FormattedArea :area="flat.area" :areaUnits="flat.areaUnits"></FormattedArea>
10
+                <FormattedRooms :rooms="flat.rooms"></FormattedRooms>
11
+                <FormattedBedrooms :bedrooms="flat.bedrooms" v-if="flat.bedrooms"></FormattedBedrooms>
12
+                <PhotosGrid :photos="flat.photos"></PhotosGrid>
13
+                <p>{{ flat.text }}</p>
14
+            </template>
15
+            <template v-else>
16
+                <h2>{{ $t('flatDetails.notFound') }}</h2>
17
+            </template>
18
+        </template>
4 19
     </div>
5 20
 </template>
6 21
 
7 22
 <script>
23
+import FormattedArea from '@/components/ui/FormattedArea.vue';
24
+import FormattedBedrooms from '@/components/ui/FormattedBedrooms.vue';
25
+import FormattedCost from '@/components/ui/FormattedCost.vue';
26
+import FormattedLocation from '@/components/ui/FormattedLocation.vue';
27
+import FormattedRooms from '@/components/ui/FormattedRooms.vue';
28
+import PhotosGrid from '@/components/ui/PhotosGrid.vue';
29
+
8 30
 export default {
31
+    components: {
32
+        FormattedArea,
33
+        FormattedBedrooms,
34
+        FormattedCost,
35
+        FormattedLocation,
36
+        FormattedRooms,
37
+        PhotosGrid,
38
+    },
9 39
     data() {
10 40
         return {
11 41
             isLoading: false,
@@ -31,6 +61,9 @@ export default {
31 61
         flat() {
32 62
             return this.$store.getters.flat(this.$route.params.flatId);
33 63
         },
64
+        flatIsNotEmpty() {
65
+            return Object.keys(this.flat).length !== 0;
66
+        },
34 67
     },
35 68
 };
36 69
 </script>

+ 13
- 26
src/components/FlatEntry.vue View File

@@ -9,7 +9,7 @@
9 9
                                 <Avatar :thumbnail="thumbnail"></Avatar>
10 10
                             </v-flex>
11 11
                             <v-flex d-flex xs12>
12
-                                <span class="body-2">{{ formattedCost }}</span>
12
+                                <FormattedCost :cost="cost" :currency="currency"></FormattedCost>
13 13
                             </v-flex>
14 14
                         </v-layout>
15 15
                     </v-flex>
@@ -21,15 +21,15 @@
21 21
                             <v-flex xs12 d-flex>
22 22
                                 <v-layout row>
23 23
                                     <v-flex xs4 class="flex-icon">
24
-                                        <v-icon>aspect_ratio</v-icon> <span v-html="formattedArea"></span>
24
+                                        <FormattedArea :area="area" :areaUnits="areaUnits"></FormattedArea>
25 25
                                     </v-flex>
26 26
                                     <v-flex xs4 class="flex-icon">
27
-                                        <v-icon>home</v-icon> {{ $tc('flatEntry.rooms', rooms, { count: rooms }) }}
27
+                                        <FormattedRooms :rooms="rooms"></FormattedRooms>
28 28
                                     </v-flex>
29 29
                                 </v-layout>
30 30
                             </v-flex>
31 31
                             <v-flex xs12 d-flex>
32
-                                <v-icon class="flex-icon">location_on</v-icon> {{ location }}
32
+                                <FormattedLocation :location="location"></FormattedLocation>
33 33
                             </v-flex>
34 34
                         </v-layout>
35 35
                     </v-flex>
@@ -57,13 +57,19 @@
57 57
 </template>
58 58
 
59 59
 <script>
60
-import currencyFormatter from 'currency-formatter';
61
-
62 60
 import Avatar from '@/components/ui/Avatar.vue';
61
+import FormattedArea from '@/components/ui/FormattedArea.vue';
62
+import FormattedCost from '@/components/ui/FormattedCost.vue';
63
+import FormattedLocation from '@/components/ui/FormattedLocation.vue';
64
+import FormattedRooms from '@/components/ui/FormattedRooms.vue';
63 65
 
64 66
 export default {
65 67
     components: {
66 68
         Avatar,
69
+        FormattedArea,
70
+        FormattedCost,
71
+        FormattedLocation,
72
+        FormattedRooms,
67 73
     },
68 74
     props: {
69 75
         id: String,
@@ -77,17 +83,6 @@ export default {
77 83
         location: String,
78 84
         urls: Array,
79 85
     },
80
-    computed: {
81
-        formattedCost() {
82
-            return currencyFormatter.format(this.cost, { code: this.currency, precision: 0 });
83
-        },
84
-        formattedArea() {
85
-            if (this.areaUnits === 'm^2') {
86
-                return `${this.area}&nbsp;m<sup>2</sup>`;
87
-            }
88
-            return this.area;
89
-        },
90
-    },
91 86
     methods: {
92 87
         goToFlatDetailsPage() {
93 88
             this.$router.push({ name: 'FlatDetails', params: { flatId: this.id } });
@@ -104,16 +99,8 @@ export default {
104 99
 };
105 100
 </script>
106 101
 
107
-<style>
102
+<style scoped>
108 103
 .flat-entry {
109 104
     cursor: pointer;
110 105
 }
111
-
112
-.flex-icon {
113
-    flex: 0 0;
114
-}
115
-
116
-.fa-icon {
117
-    font-size: 20px;
118
-}
119 106
 </style>

+ 26
- 0
src/components/ui/FormattedArea.vue View File

@@ -0,0 +1,26 @@
1
+<template>
2
+    <span>
3
+        <v-icon v-if="withIcon">aspect_ratio</v-icon> <span v-html="formattedArea"></span>
4
+    </span>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+    props: {
10
+        area: Number,
11
+        areaUnits: String,
12
+        withIcon: {
13
+            type: Boolean,
14
+            default: true,
15
+        },
16
+    },
17
+    computed: {
18
+        formattedArea() {
19
+            if (this.areaUnits === 'm^2') {
20
+                return `${this.area}&nbsp;m<sup>2</sup>`;
21
+            }
22
+            return this.area;
23
+        },
24
+    },
25
+};
26
+</script>

+ 17
- 0
src/components/ui/FormattedBedrooms.vue View File

@@ -0,0 +1,17 @@
1
+<template>
2
+    <span>
3
+        <v-icon v-if="withIcon" class="fa-icon">fa-bed</v-icon> {{ $tc('flatEntry.bedrooms', bedrooms, { count: bedrooms }) }}
4
+    </span>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+    props: {
10
+        bedrooms: Number,
11
+        withIcon: {
12
+            type: Boolean,
13
+            default: true,
14
+        },
15
+    },
16
+};
17
+</script>

+ 19
- 0
src/components/ui/FormattedCost.vue View File

@@ -0,0 +1,19 @@
1
+<template>
2
+    <span class="body-2">{{ formattedCost }}</span>
3
+</template>
4
+
5
+<script>
6
+import currencyFormatter from 'currency-formatter';
7
+
8
+export default {
9
+    props: {
10
+        cost: Number,
11
+        currency: String,
12
+    },
13
+    computed: {
14
+        formattedCost() {
15
+            return currencyFormatter.format(this.cost, { code: this.currency, precision: 0 });
16
+        },
17
+    },
18
+};
19
+</script>

+ 17
- 0
src/components/ui/FormattedLocation.vue View File

@@ -0,0 +1,17 @@
1
+<template>
2
+    <span>
3
+        <v-icon class="flex-icon" v-if="withIcon">location_on</v-icon> {{ location }}
4
+    </span>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+    props: {
10
+        location: String,
11
+        withIcon: {
12
+            type: Boolean,
13
+            default: true,
14
+        },
15
+    },
16
+};
17
+</script>

+ 17
- 0
src/components/ui/FormattedRooms.vue View File

@@ -0,0 +1,17 @@
1
+<template>
2
+    <span>
3
+        <v-icon v-if="withIcon">home</v-icon> {{ $tc('flatEntry.rooms', rooms, { count: rooms }) }}
4
+    </span>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+    props: {
10
+        rooms: Number,
11
+        withIcon: {
12
+            type: Boolean,
13
+            default: true,
14
+        },
15
+    },
16
+};
17
+</script>

+ 40
- 0
src/components/ui/PhotosGrid.vue View File

@@ -0,0 +1,40 @@
1
+<template>
2
+    <isotope ref="cpt" :list="photos" :options="options" v-images-loaded:on.progress="layout">
3
+        <div v-for="photo in photos" :key="photo.url">
4
+            <img :src="photo.url">
5
+        </div>
6
+    </isotope>
7
+</template>
8
+
9
+<script>
10
+import imagesLoaded from 'vue-images-loaded';
11
+import isotope from 'vueisotope';
12
+
13
+export default {
14
+    components: {
15
+        isotope,
16
+    },
17
+    directives: {
18
+        imagesLoaded,
19
+    },
20
+    props: {
21
+        photos: Array,
22
+    },
23
+    data() {
24
+        return {
25
+            options: {},
26
+        };
27
+    },
28
+    methods: {
29
+        layout() {
30
+            this.$refs.cpt.layout('masonry');
31
+        },
32
+    },
33
+};
34
+</script>
35
+
36
+<style scoped>
37
+img {
38
+    max-height: 200px;
39
+}
40
+</style>

+ 1
- 0
src/constants.js View File

@@ -1 +1,2 @@
1 1
 export const BASE_API_URL = 'http://localhost:8080';
2
+export const ITEMS_PER_PAGE = 10;

+ 4
- 0
src/i18n/index.js View File

@@ -2,6 +2,10 @@ const messages = {
2 2
     fr: {
3 3
         flatEntry: {
4 4
             rooms: '1 pièce | {count} pièces',
5
+            bedrooms: '1 chambre | {count} chambres',
6
+        },
7
+        flatDetails: {
8
+            notFound: 'Annonce introuvable',
5 9
         },
6 10
     },
7 11
 };

+ 1
- 1
src/router/index.js View File

@@ -5,7 +5,7 @@ export default ({
5 5
     routes: [
6 6
         {
7 7
             path: '/',
8
-            name: 'FlatList',
8
+            name: 'Home',
9 9
             component: FlatList,
10 10
         },
11 11
         {

+ 1
- 1
src/store/actions.js View File

@@ -5,7 +5,7 @@ export default {
5 5
     getFlats({ commit }) {
6 6
         return api.getFlats().then(flats => commit(types.STORE_FLATS, { flats }));
7 7
     },
8
-    getFlat({ commit }, { flatId }) {
8
+    getFlat({ commit }, flatId) {
9 9
         return api.getFlat(flatId).then(
10 10
             flat => commit(types.MERGE_FLATS, { flats: [flat] }),
11 11
         );