Skip to content

Commit 90ad76c

Browse files
committed
UI for history page
1 parent 5e784e4 commit 90ad76c

File tree

5 files changed

+367
-1
lines changed

5 files changed

+367
-1
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<template>
2+
<!-- eslint-disable max-len -->
3+
<section class="mb-4 sm:mb-16 lg:mb-32">
4+
<div class="flex items-center justify-center md:justify-start w-full mb-4">
5+
<h1
6+
class="text-3xl sm:text-5xl my-4 inline-block text-center leading-none onboarding-sub-container-content-heading">
7+
{{recommendations.length}}</h1>
8+
<DropdownDisplayChoiceHistory
9+
v-on:save-single-choice="saveSingleChoice"
10+
class="inline-block ml-4"
11+
v-bind:name="'history'"
12+
v-bind:starting-option="'People I viewed'"
13+
v-bind:options="['People I view', 'People I like', 'Who views me', 'Who likes me', 'Whom I block']"></DropdownDisplayChoiceHistory>
14+
</div>
15+
<div class="flex w-full items-stretch sm:items-center justify-center md:justify-start mb-12 relative">
16+
<Sort
17+
v-bind:position="'left'"
18+
v-bind:startingOption="'Closest'"
19+
v-bind:options="['Closest', 'Furthest', 'Youngest',
20+
'Oldest', 'Most popular', 'Least popular', 'Most common interests', 'Least common interests']"
21+
v-on:save-sort="saveSort"></Sort>
22+
<FilterSliderDropdown
23+
v-bind:min="recommendationsAnalysis.age.min"
24+
v-bind:max="recommendationsAnalysis.age.max"
25+
v-bind:name="'age'"
26+
v-on:save-filter="saveFilter"></FilterSliderDropdown>
27+
<FilterSliderDropdown
28+
v-bind:min="recommendationsAnalysis.distance.min"
29+
v-bind:max="recommendationsAnalysis.distance.max"
30+
v-bind:unit="'km'"
31+
v-bind:name="'distance'"
32+
v-on:save-filter="saveFilter"></FilterSliderDropdown>
33+
<FilterSliderDropdown
34+
v-bind:min="recommendationsAnalysis.popularity.min"
35+
v-bind:max="recommendationsAnalysis.popularity.max"
36+
v-bind:unit="'pts'"
37+
v-bind:name="'popularity'"
38+
v-on:save-filter="saveFilter"></FilterSliderDropdown>
39+
<MultipleFiltersDropdown
40+
v-bind:position="'right'"
41+
v-bind:options="recommendationsAnalysis.interests"
42+
v-bind:name="'interests'"
43+
v-on:save-filter-multiple="saveFilterMultiple"></MultipleFiltersDropdown>
44+
</div>
45+
<div ref="recommendationCards" class="grid grid-cols-1 md:grid-cols-2 gap-2">
46+
<RecommendationCard
47+
v-for="(recommendation, index) in recommendations" :key="index"
48+
v-bind:recommendation="recommendation"></RecommendationCard>
49+
</div>
50+
</section>
51+
</template>
52+
53+
<script>
54+
/* eslint-disable */
55+
import Sort from '@/components/shared/Sort.vue';
56+
import FilterSliderDropdown from '@/components/shared/FilterSliderDropdown.vue';
57+
import RecommendationCard from '@/components/app/recommendations/RecommendationCard.vue';
58+
import MultipleFiltersDropdown from '@/components/shared/MultipleFiltersDropdown.vue';
59+
import DropdownDisplayChoiceHistory from '@/components/shared/DropdownDisplayChoiceHistory.vue';
60+
61+
export default {
62+
props: ['title', 'recommendationsReceived', 'recommendationsAnalysis'],
63+
components: {
64+
Sort,
65+
RecommendationCard,
66+
FilterSliderDropdown,
67+
MultipleFiltersDropdown,
68+
DropdownDisplayChoiceHistory,
69+
},
70+
data: () => ({
71+
recommendations: [],
72+
recommendationsBackup: [],
73+
sorting: 'Closest',
74+
filters: {
75+
age: {
76+
min: null,
77+
max: null,
78+
},
79+
distance: {
80+
min: null,
81+
max: null,
82+
},
83+
popularity: {
84+
min: null,
85+
max: null,
86+
},
87+
interests: [],
88+
},
89+
}),
90+
methods: {
91+
saveSort(...args) {
92+
const [by] = args;
93+
this.sorting = by;
94+
},
95+
saveFilter(...range) {
96+
const [name, min, max] = range;
97+
this.filters[name].min = min;
98+
this.filters[name].max = max;
99+
},
100+
saveFilterMultiple(...multiple) {
101+
const [name, filters] = multiple;
102+
this.filters[name] = filters;
103+
},
104+
sort(recommendations, by) {
105+
if (by === 'Closest') {
106+
recommendations.sort((a, b) => a.distance - b.distance);
107+
} else if (by === 'Furthest') {
108+
recommendations.sort((a, b) => b.distance - a.distance);
109+
} else if (by === 'Youngest') {
110+
recommendations.sort((a, b) => a.age - b.age);
111+
} else if (by === 'Oldest') {
112+
recommendations.sort((a, b) => b.age - a.age);
113+
} else if (by === 'Most popular') {
114+
recommendations.sort((a, b) => b.heat_score - a.heat_score);
115+
} else if (by === 'Least popular') {
116+
recommendations.sort((a, b) => a.heat_score - b.heat_score);
117+
} else if (by === 'Most common interests') {
118+
recommendations.sort((a, b) => b.common_tags.length - a.common_tags.length);
119+
} else if (by === 'Least common interests') {
120+
recommendations.sort((a, b) => a.common_tags.length - b.common_tags.length);
121+
}
122+
},
123+
filter(recommendations) {
124+
let i = recommendations.length;
125+
while (i--) {
126+
if (!this.meetsFilters(recommendations[i])) {
127+
recommendations.splice(i, 1);
128+
}
129+
}
130+
return recommendations;
131+
},
132+
meetsFilters(recommendation) {
133+
return this.meetsAge(recommendation)
134+
&& this.meetsPopularity(recommendation)
135+
&& this.meetsDistance(recommendation)
136+
&& this.meetsInterests(recommendation);
137+
},
138+
meetsAge(recommendation) {
139+
return recommendation.age >= this.filters.age.min
140+
&& recommendation.age <= this.filters.age.max;
141+
},
142+
meetsPopularity(recommendation) {
143+
return recommendation.heat_score >= this.filters.popularity.min
144+
&& recommendation.heat_score <= this.filters.popularity.max;
145+
},
146+
meetsDistance(recommendation) {
147+
return recommendation.distance >= this.filters.distance.min
148+
&& recommendation.distance <= this.filters.distance.max;
149+
},
150+
meetsInterests(recommendation) {
151+
const recommendationInterests = recommendation.interests;
152+
const filterInterests = this.filters.interests;
153+
for (let i = 0; i < filterInterests.length; i += 1) {
154+
if (recommendationInterests.indexOf(filterInterests[i]) === -1) {
155+
return false;
156+
}
157+
}
158+
return true;
159+
},
160+
filterOutBlockedUsers(recommendations) {
161+
let i = recommendations.length;
162+
const { blocks } = this.$store.getters.getLoggedInUser;
163+
let blockedIds = [];
164+
for (let j = 0; j < blocks.length; j += 1) {
165+
blockedIds.push(blocks[j].blocked_id);
166+
}
167+
while (i--) {
168+
if (blockedIds.indexOf(recommendations[i].id) !== -1) {
169+
recommendations.splice(i, 1);
170+
}
171+
}
172+
return recommendations;
173+
},
174+
saveSingleChoice() {
175+
console.log('bingo');
176+
},
177+
},
178+
watch: {
179+
sorting: {
180+
handler() {
181+
this.sort(this.recommendations, this.sorting);
182+
},
183+
},
184+
filters: {
185+
handler() {
186+
let base = this.recommendationsBackup.slice();
187+
base = this.filter(base);
188+
this.sort(base, this.sorting);
189+
this.recommendations = base;
190+
},
191+
deep: true,
192+
}
193+
},
194+
beforeMount() {
195+
this.recommendations = this.filterOutBlockedUsers(this.recommendationsReceived);
196+
this.recommendationsBackup = this.recommendations;
197+
this.filters.age.min = this.recommendationsAnalysis.age.min;
198+
this.filters.age.max = this.recommendationsAnalysis.age.max;
199+
this.filters.distance.min = this.recommendationsAnalysis.distance.min;
200+
this.filters.distance.max = this.recommendationsAnalysis.distance.max;
201+
this.filters.popularity.min = this.recommendationsAnalysis.popularity.min;
202+
this.filters.popularity.max = this.recommendationsAnalysis.popularity.max;
203+
}
204+
};
205+
</script>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<template>
2+
<!-- eslint-disable max-len -->
3+
<div class="focus:outline-none sm:flex-none cursor-pointer" @focusout="close" tabindex="0">
4+
<div v-bind:class="{'onboarding-sub-container-content-heading': true, 'border': true, 'px-4': true, 'py-2': true, 'rounded-md': true, 'text-center': true, 'text-2xl': true, 'sm:text-5xl': true, 'border-gray-matcha': !closed}" @click="toggle">
5+
<h1 v-bind:class="{ 'opacity-100': closed, 'noSelect': true, 'capitalize': true }">{{ currentOption }}<span v-if="closed" class="ml-2">▼</span><span v-if="!closed" class="ml-2">▲</span></h1>
6+
</div>
7+
<div v-bind:class="{'sort-dropdown': true, 'max-w-xs': true, 'mx-auto': true, 'left-0': position === 'left' || position === 'center', 'right-0': position === 'right' || position === 'center', 'z-10': true, 'hidden': closed, 'h-auto': options.length < 5}">
8+
<h1 v-for="(option, index) in options" :key="option + index + option"
9+
v-bind:class="{'capitalize': true, 'sort-dropdown-option': true, 'border-b': index !== options.length - 1, 'font-extrabold': option === currentOption, 'text-gray-matcha': option === currentOption}"
10+
v-on:click="select(option)">
11+
{{option}}
12+
</h1>
13+
</div>
14+
</div>
15+
</template>
16+
17+
<script>
18+
export default {
19+
props: ['options', 'name', 'position', 'startingOption'],
20+
data: () => ({
21+
closed: true,
22+
currentOption: '',
23+
}),
24+
methods: {
25+
select(option) {
26+
this.close();
27+
this.currentOption = option;
28+
this.$emit('save-single-choice', this.name, option);
29+
},
30+
toggle() {
31+
this.closed = !this.closed;
32+
},
33+
close() {
34+
this.closed = true;
35+
},
36+
},
37+
beforeMount() {
38+
this.currentOption = this.startingOption;
39+
},
40+
};
41+
</script>

frontend/src/components/shared/NavBar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<router-link to="/">
88
<div class="flex justify-center items-center">
99
<img src="../../assets/logo.png" class="h-6">
10-
<h1 class="invisible lg:visible text-purple-matcha text-xl ml-2">Matcha</h1>
10+
<h1 v-if="!loggedIn" class="text-purple-matcha text-xl ml-2">Matcha</h1>
1111
</div>
1212
</router-link>
1313
</div>

frontend/src/router/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Browse from '../views/app/Browse.vue';
1313
import Search from '../views/app/Search.vue';
1414
import Settings from '../views/app/Settings.vue';
1515
import User from '../components/app/users/User.vue';
16+
import History from '../views/app/History.vue';
1617
import store from '../store/index';
1718

1819
Vue.use(VueRouter);
@@ -120,6 +121,11 @@ const routes = [
120121
component: User,
121122
beforeEnter: notLoggedInRedirectLogin,
122123
},
124+
{
125+
path: '/history',
126+
component: History,
127+
beforeEnter: notLoggedInRedirectLogin,
128+
},
123129
];
124130

125131
const router = new VueRouter({

frontend/src/views/app/History.vue

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<template>
2+
<!-- eslint-disable max-len -->
3+
<div class="mx-4 sm:mx-16 lg:mx-32">
4+
<NavBar v-bind:currentRoute="'History'"></NavBar>
5+
<section class="mx-auto">
6+
<HistoryRecommendations
7+
v-if="recommendationsAnalysisDone"
8+
v-bind:title="'Potential matches'"
9+
v-bind:recommendationsReceived="recommendations"
10+
v-bind:recommendationsAnalysis="recommendationsAnalysis"></HistoryRecommendations>
11+
</section>
12+
</div>
13+
</template>
14+
15+
<script>
16+
/* eslint-disable max-len */
17+
18+
import NavBar from '@/components/shared/NavBar.vue';
19+
import HistoryRecommendations from '@/components/app/recommendations/HistoryRecommendations.vue';
20+
21+
export default {
22+
name: 'Browse',
23+
props: ['recommendationsFromSettingUp'],
24+
components: {
25+
NavBar,
26+
HistoryRecommendations,
27+
},
28+
data: () => ({
29+
recommendations: [],
30+
recommendationsAnalysis: {
31+
age: {
32+
min: null,
33+
max: null,
34+
},
35+
distance: {
36+
min: null,
37+
max: null,
38+
},
39+
popularity: {
40+
min: null,
41+
max: null,
42+
},
43+
interests: [],
44+
},
45+
recommendationsAnalysisDone: false,
46+
}),
47+
methods: {
48+
async fetchUsers() {
49+
if (this.recommendationsFromSettingUp) {
50+
this.recommendations = this.recommendationsFromSettingUp;
51+
} else {
52+
const recommendationsRequest = await this.$http.get('/recommendations');
53+
this.recommendations = recommendationsRequest.data.recommendations;
54+
}
55+
this.recommendations.sort((a, b) => a.distance - b.distance);
56+
for (let i = 0; i < this.recommendations.length; i += 1) {
57+
this.recommendations[i].distance = Math.floor(this.recommendations[i].distance);
58+
if (this.recommendations[i].age < 18) {
59+
this.recommendations[i].age = 18;
60+
}
61+
if (this.recommendationsAnalysis.age.min === null || this.recommendations[i].age < this.recommendationsAnalysis.age.min) {
62+
this.recommendationsAnalysis.age.min = this.recommendations[i].age;
63+
}
64+
if (this.recommendationsAnalysis.age.max === null || this.recommendations[i].age > this.recommendationsAnalysis.age.max) {
65+
this.recommendationsAnalysis.age.max = this.recommendations[i].age;
66+
}
67+
if (this.recommendationsAnalysis.distance.min === null || this.recommendations[i].distance < this.recommendationsAnalysis.distance.min) {
68+
this.recommendationsAnalysis.distance.min = this.recommendations[i].distance;
69+
}
70+
if (this.recommendationsAnalysis.distance.max === null || this.recommendations[i].distance > this.recommendationsAnalysis.distance.max) {
71+
this.recommendationsAnalysis.distance.max = this.recommendations[i].distance;
72+
}
73+
if (this.recommendationsAnalysis.popularity.min === null || this.recommendations[i].heat_score < this.recommendationsAnalysis.popularity.min) {
74+
this.recommendationsAnalysis.popularity.min = this.recommendations[i].heat_score;
75+
}
76+
if (this.recommendationsAnalysis.popularity.max === null || this.recommendations[i].heat_score > this.recommendationsAnalysis.popularity.max) {
77+
this.recommendationsAnalysis.popularity.max = this.recommendations[i].heat_score;
78+
}
79+
const interests = this.recommendations[i].tags;
80+
this.recommendations[i].interests = [];
81+
for (let j = 0; j < interests.length; j += 1) {
82+
this.recommendations[i].interests.push(interests[j].name);
83+
if (this.recommendationsAnalysis.interests.indexOf(interests[j].name) === -1) {
84+
this.recommendationsAnalysis.interests.push(interests[j].name);
85+
}
86+
}
87+
}
88+
this.recommendationsAnalysisDone = true;
89+
},
90+
browseAgain() {
91+
this.recommendations = [];
92+
this.recommendationsAnalysis.age.min = null;
93+
this.recommendationsAnalysis.age.max = null;
94+
this.recommendationsAnalysis.distance.min = null;
95+
this.recommendationsAnalysis.distance.max = null;
96+
this.recommendationsAnalysis.popularity.min = null;
97+
this.recommendationsAnalysis.popularity.max = null;
98+
this.recommendationsAnalysis.interests = [];
99+
this.recommendationsAnalysisDone = false;
100+
},
101+
},
102+
async created() {
103+
await this.fetchUsers();
104+
},
105+
deactivated() {
106+
if (!this.$route.path.startsWith('/users')) {
107+
this.browseAgain();
108+
this.fetchUsers();
109+
this.$el.scrollTop = 0;
110+
}
111+
},
112+
};
113+
114+
</script>

0 commit comments

Comments
 (0)