UX: Add tabs to filter between different types of RSVPed guests (#174)

* UX: Adding tabs to invitees modal

Co-authored-by: Jarek Radosz <jradosz@gmail.com>
This commit is contained in:
Jordan Vidrine 2021-09-28 14:49:02 -05:00 committed by GitHub
parent edaeeb2402
commit 8bd20e6636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 175 additions and 26 deletions

View File

@ -13,6 +13,10 @@ module DiscoursePostEvent
.where('LOWER(users.username) LIKE :filter', filter: "%#{params[:filter].downcase}%")
end
if params[:type]
event_invitees = event_invitees.with_status(params[:type].to_sym)
end
render json: ActiveModel::ArraySerializer.new(
event_invitees.order([:status, :user_id]).limit(200),
each_serializer: InviteeSerializer

View File

@ -0,0 +1,23 @@
import Component from "@ember/component";
import { readOnly } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "",
viewingType: readOnly("modal.type"),
@discourseComputed("viewingType")
isGoing(viewingType) {
return viewingType === "going" ? " btn-danger" : " btn-default";
},
@discourseComputed("viewingType")
isInterested(viewingType) {
return viewingType === "interested" ? " btn-danger" : " btn-default";
},
@discourseComputed("viewingType")
isNotGoing(viewingType) {
return viewingType === "not_going" ? " btn-danger" : " btn-default";
},
});

View File

@ -7,10 +7,20 @@ export default Controller.extend(ModalFunctionality, {
invitees: null,
filter: null,
isLoading: false,
type: "going",
onShow() {
this._fetchInvitees();
},
@action
toggleViewingFilter(filter) {
this.onFilterChanged(filter);
},
@action
toggleType(type) {
this.set("type", type);
this._fetchInvitees(this.filter);
},
@action
onFilterChanged(filter) {
@ -21,7 +31,7 @@ export default Controller.extend(ModalFunctionality, {
debounceFunc = require("discourse-common/lib/debounce").default;
} catch (_) {}
debounceFunc(this, this._fetchInvitees, filter, 250);
debounceFunc(this, this._fetchInvitees, filter, this.type, 250);
},
@action
@ -36,6 +46,7 @@ export default Controller.extend(ModalFunctionality, {
.findAll("discourse-post-event-invitee", {
filter,
post_id: this.model.id,
type: this.type,
})
.then((invitees) => this.set("invitees", invitees))
.finally(() => this.set("isLoading", false));

View File

@ -0,0 +1,22 @@
<div class="invitees-type-filter">
{{d-button
label="discourse_post_event.models.invitee.status.going"
class=(concat "btn toggle-going" isGoing)
action=toggle
actionParam="going"
}}
{{d-button
label="discourse_post_event.models.invitee.status.interested"
class=(concat "btn toggle-interested" isInterested)
action=toggle
actionParam="interested"
}}
{{d-button
label="discourse_post_event.models.invitee.status.not_going"
class=(concat "btn toggle-not-going" isNotGoing)
action=toggle
actionParam="not_going"
}}
</div>

View File

@ -5,8 +5,9 @@
class="filter"
placeholderKey="discourse_post_event.invitees_modal.filter_placeholder"
}}
{{toggle-invitees-modal modal=this toggle=(action "toggleType")}}
{{#conditional-loading-spinner condition=isLoading}}
{{#if invitees}}
<ul class="invitees">
{{#each invitees as |invitee|}}
<li class="invitee">
@ -31,5 +32,10 @@
</li>
{{/each}}
</ul>
{{else}}
<p class="no-users">
{{i18n "discourse_post_event.models.invitee.no_users"}}
</p>
{{/if}}
{{/conditional-loading-spinner}}
{{/d-modal-body}}

View File

@ -1,10 +1,35 @@
.discourse-post-event-invitees-modal {
.modal-body {
padding: 0;
}
.modal-inner-container {
min-width: 350px;
}
.loading-container {
height: 40vh;
overflow-y: scroll;
padding: 0 1em 9px 1em;
.no-users {
text-align: center;
font-size: var(--font-up-1);
}
}
.invitees-type-filter {
margin-bottom: 9px;
display: flex;
.btn {
width: calc(100% / 3);
margin: 0;
border-radius: 0px;
padding: 0.75em 0em;
}
}
.filter {
width: 100%;
width: calc(100% - 2em);
margin: 1em;
}
.invitees {
display: flex;
padding: 0;
margin: 0;
flex-direction: column;

View File

@ -288,6 +288,7 @@ en:
ends_in_duration: "Ends %{duration}"
models:
invitee:
no_users: "There are no users of this type."
status:
unknown: "Not interested"
going: "Going"

View File

@ -16,6 +16,63 @@ module DiscoursePostEvent
let(:topic_1) { Fabricate(:topic, user: user) }
let(:post_1) { Fabricate(:post, user: user, topic: topic_1) }
describe "#index" do
context 'when params are included' do
let(:invitee1) { Fabricate(:user, username: "Francis", name: "Francis") }
let(:invitee2) { Fabricate(:user, username: "Francisco", name: "Francisco") }
let(:invitee3) { Fabricate(:user, username: "Frank", name: "Frank") }
let(:invitee4) { Fabricate(:user, username: "Franchesca", name: "Franchesca") }
let(:post_event_1) {
pe = Fabricate(:event, post: post_1)
pe.create_invitees([{
user_id: invitee1.id,
status: Invitee.statuses[:going]
},
{
user_id: invitee2.id,
status: Invitee.statuses[:interested]
},
{
user_id: invitee3.id,
status: Invitee.statuses[:not_going]
},
{
user_id: invitee4.id,
status: Invitee.statuses[:going]
}])
pe
}
it 'returns the correct amount of users when filtering the invitees by name' do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json", params: {
filter: "Franc"
}
filteredInvitees = response.parsed_body["invitees"]
expect(filteredInvitees.count).to eq(3)
end
it 'returns the correct amount of users when filtering the invitees by type' do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json", params: {
type: "interested"
}
filteredInvitees = response.parsed_body["invitees"]
expect(filteredInvitees.count).to eq(1)
end
it 'returns the correct amount of users when filtering the invitees by name and type' do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json", params: {
filter: "Franc",
type: "going"
}
filteredInvitees = response.parsed_body["invitees"]
expect(filteredInvitees.count).to eq(2)
end
end
end
context 'when a post event exists' do
context 'when an invitee exists' do
let(:invitee1) { Fabricate(:user) }