mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Added dynamic value selection UI for filter dropdown
refs https://github.com/TryGhost/Team/issues/943 The filter UI behind labs in Admin allows filtering members list across several filters. Since each filter type can have its own specific set of values to choose from, this change adds custom UI based on filter type to select filter value.
This commit is contained in:
parent
e224c96bba
commit
0aa7aca560
6 changed files with 185 additions and 21 deletions
|
@ -44,10 +44,9 @@
|
||||||
/>
|
/>
|
||||||
{{svg-jar "arrow-down-small"}}
|
{{svg-jar "arrow-down-small"}}
|
||||||
</span>
|
</span>
|
||||||
<GhTextInput
|
<GhMembersFilterValueLabs
|
||||||
@name="filter-value-1"
|
@filter={{filter}}
|
||||||
@value={{filter.value}}
|
@setFilterValue={{this.setFilterValue}}
|
||||||
@input={{fn this.setFilterValue filter.id}}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
@ -9,7 +9,7 @@ const FILTER_PROPERTIES = [
|
||||||
{label: 'Name', name: 'name', group: 'Basic'},
|
{label: 'Name', name: 'name', group: 'Basic'},
|
||||||
{label: 'Email', name: 'email', group: 'Basic'},
|
{label: 'Email', name: 'email', group: 'Basic'},
|
||||||
// {label: 'Location', name: 'location', group: 'Basic'},
|
// {label: 'Location', name: 'location', group: 'Basic'},
|
||||||
{label: 'Newsletter subscription status', name: 'subscribed', group: 'Basic'},
|
{label: 'Newsletter subscription', name: 'subscribed', group: 'Basic'},
|
||||||
{label: 'Label', name: 'label', group: 'Basic'},
|
{label: 'Label', name: 'label', group: 'Basic'},
|
||||||
|
|
||||||
// Member subscription
|
// Member subscription
|
||||||
|
@ -71,9 +71,15 @@ export default class GhMembersFilterLabsComponent extends Component {
|
||||||
generateNqlFilter(filters) {
|
generateNqlFilter(filters) {
|
||||||
let query = '';
|
let query = '';
|
||||||
filters.forEach((filter) => {
|
filters.forEach((filter) => {
|
||||||
const relationStr = filter.relation === 'is-not' ? '-' : '';
|
if (filter.type === 'label') {
|
||||||
const filterValue = filter.value.includes(' ') ? `'${filter.value}'` : filter.value;
|
const relationStr = filter.relation === 'is-not' ? '-' : '';
|
||||||
query += `${filter.type}:${relationStr}${filterValue}+`;
|
const filterValue = '[' + filter.value.join(',') + ']';
|
||||||
|
query += `${filter.type}:${relationStr}${filterValue}+`;
|
||||||
|
} else {
|
||||||
|
const relationStr = filter.relation === 'is-not' ? '-' : '';
|
||||||
|
const filterValue = filter.value.includes(' ') ? `'${filter.value}'` : filter.value;
|
||||||
|
query += `${filter.type}:${relationStr}${filterValue}+`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return query.slice(0, -1);
|
return query.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +94,7 @@ export default class GhMembersFilterLabsComponent extends Component {
|
||||||
setFilterType(filterId, newType) {
|
setFilterType(filterId, newType) {
|
||||||
const filterToEdit = this.filters.findBy('id', filterId);
|
const filterToEdit = this.filters.findBy('id', filterId);
|
||||||
filterToEdit.set('type', newType);
|
filterToEdit.set('type', newType);
|
||||||
|
filterToEdit.set('value', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -97,9 +104,13 @@ export default class GhMembersFilterLabsComponent extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
setFilterValue(filterId, event) {
|
setFilterValue(filterType, filterId, filterValue) {
|
||||||
const filterToEdit = this.filters.findBy('id', filterId);
|
const filterToEdit = this.filters.findBy('id', filterId);
|
||||||
filterToEdit.set('value', event.target.value);
|
if (filterType === 'label') {
|
||||||
|
filterToEdit.set('value', filterValue);
|
||||||
|
} else {
|
||||||
|
filterToEdit.set('value', filterValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
85
ghost/admin/app/components/gh-members-filter-value-labs.hbs
Normal file
85
ghost/admin/app/components/gh-members-filter-value-labs.hbs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
{{#if (eq @filter.type 'label')}}
|
||||||
|
<GhMemberLabelInput
|
||||||
|
@onChange={{fn this.setLabelsFilterValue @filter.type @filter.id}}
|
||||||
|
@triggerId="label-input"
|
||||||
|
data-test-input=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{else if (eq @filter.type 'status')}}
|
||||||
|
<span class="gh-select">
|
||||||
|
<OneWaySelect
|
||||||
|
@value={{filter.value}}
|
||||||
|
@options={{this.availableFilterOptions.status}}
|
||||||
|
@optionValuePath="name"
|
||||||
|
@optionLabelPath="label"
|
||||||
|
@optionTargetPath="name"
|
||||||
|
@update={{fn this.setFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
{{svg-jar "arrow-down-small"}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{else if (eq @filter.type 'email_count')}}
|
||||||
|
<GhTextInput
|
||||||
|
@value={{@filter.value}}
|
||||||
|
@type="number"
|
||||||
|
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{else if (eq @filter.type 'email_opened_count')}}
|
||||||
|
<GhTextInput
|
||||||
|
@value={{@filter.value}}
|
||||||
|
@type="number"
|
||||||
|
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{else if (eq @filter.type 'email_open_rate')}}
|
||||||
|
<GhTextInput
|
||||||
|
@value={{@filter.value}}
|
||||||
|
@type="number"
|
||||||
|
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{else if (eq @filter.type 'subscriptions.plan_interval')}}
|
||||||
|
<span class="gh-select">
|
||||||
|
<OneWaySelect
|
||||||
|
@value={{filter.value}}
|
||||||
|
@options={{this.availableFilterOptions.subscriptionPriceInterval}}
|
||||||
|
@optionValuePath="name"
|
||||||
|
@optionLabelPath="label"
|
||||||
|
@optionTargetPath="name"
|
||||||
|
@update={{fn this.setFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
{{svg-jar "arrow-down-small"}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{else if (eq @filter.type 'subscriptions.status')}}
|
||||||
|
<span class="gh-select">
|
||||||
|
<OneWaySelect
|
||||||
|
@value={{filter.value}}
|
||||||
|
@options={{this.availableFilterOptions.subscriptionStripeStatus}}
|
||||||
|
@optionValuePath="name"
|
||||||
|
@optionLabelPath="label"
|
||||||
|
@optionTargetPath="name"
|
||||||
|
@update={{fn this.setFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
{{svg-jar "arrow-down-small"}}
|
||||||
|
</span>
|
||||||
|
{{else if (eq @filter.type 'subscribed')}}
|
||||||
|
<span class="gh-select">
|
||||||
|
<OneWaySelect
|
||||||
|
@value={{filter.value}}
|
||||||
|
@options={{this.availableFilterOptions.subscribed}}
|
||||||
|
@optionValuePath="name"
|
||||||
|
@optionLabelPath="label"
|
||||||
|
@optionTargetPath="name"
|
||||||
|
@update={{fn this.setFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
{{svg-jar "arrow-down-small"}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<GhTextInput
|
||||||
|
@name={{@filter.id}}
|
||||||
|
@value={{@filter.value}}
|
||||||
|
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
49
ghost/admin/app/components/gh-members-filter-value-labs.js
Normal file
49
ghost/admin/app/components/gh-members-filter-value-labs.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
|
||||||
|
const FILTER_OPTIONS = {
|
||||||
|
subscriptionPriceInterval: [
|
||||||
|
{label: 'Monthly', name: 'month'},
|
||||||
|
{label: 'Yearly', name: 'year'}
|
||||||
|
],
|
||||||
|
status: [
|
||||||
|
{label: 'Paid', name: 'paid'},
|
||||||
|
{label: 'Free', name: 'free'},
|
||||||
|
{label: 'Complimentary', name: 'comped'}
|
||||||
|
],
|
||||||
|
subscribed: [
|
||||||
|
{label: 'Subscribed', name: 'true'},
|
||||||
|
{label: 'Unsubscribed', name: 'false'}
|
||||||
|
],
|
||||||
|
subscriptionStripeStatus: [
|
||||||
|
{label: 'Active', name: 'active'},
|
||||||
|
{label: 'Trialing', name: 'trialing'},
|
||||||
|
{label: 'Canceled', name: 'canceled'},
|
||||||
|
{label: 'Unpaid', name: 'unpaid'},
|
||||||
|
{label: 'Past Due', name: 'past_due'},
|
||||||
|
{label: 'Incomplete', name: 'incomplete'},
|
||||||
|
{label: 'Incomplete - Expired', name: 'incomplete_expired'}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class GhMembersFilterValueLabs extends Component {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.availableFilterOptions = FILTER_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setInputFilterValue(filterType, filterId, event) {
|
||||||
|
this.args.setFilterValue(filterType, filterId, event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setLabelsFilterValue(filterType, filterId, labels) {
|
||||||
|
this.args.setFilterValue(filterType, filterId, labels.map(label => label.slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setFilterValue(filterType, filterId, value) {
|
||||||
|
this.args.setFilterValue(filterType, filterId, value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,8 @@
|
||||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
<span class="midlightgrey">{{labels}}</span>
|
<span class="midlightgrey">{{labels}}</span>
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (eq @filterColumn 'status')}}
|
{{else if (eq @filterColumn 'status')}}
|
||||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
{{#if (not (is-empty @member.status))}}
|
{{#if (not (is-empty @member.status))}}
|
||||||
<span class="gh-members-list-open-rate-mobile">{{capitalize @member.status}}</span>
|
<span class="gh-members-list-open-rate-mobile">{{capitalize @member.status}}</span>
|
||||||
|
@ -12,9 +11,8 @@
|
||||||
<span class="midlightgrey">-</span>
|
<span class="midlightgrey">-</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (eq @filterColumn 'email_count')}}
|
{{else if (eq @filterColumn 'email_count')}}
|
||||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
{{#if (not (is-empty @member.emailCount))}}
|
{{#if (not (is-empty @member.emailCount))}}
|
||||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailCount}}</span>
|
<span class="gh-members-list-open-rate-mobile">{{@member.emailCount}}</span>
|
||||||
|
@ -22,9 +20,8 @@
|
||||||
<span class="midlightgrey">-</span>
|
<span class="midlightgrey">-</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (eq @filterColumn 'email_opened_count')}}
|
{{else if (eq @filterColumn 'email_opened_count')}}
|
||||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
{{#if (not (is-empty @member.emailOpenedCount))}}
|
{{#if (not (is-empty @member.emailOpenedCount))}}
|
||||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenedCount}}</span>
|
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenedCount}}</span>
|
||||||
|
@ -32,9 +29,8 @@
|
||||||
<span class="midlightgrey">-</span>
|
<span class="midlightgrey">-</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (eq @filterColumn 'subscribed')}}
|
{{else if (eq @filterColumn 'subscribed')}}
|
||||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
{{#if (not (is-empty @member.subscribed))}}
|
{{#if (not (is-empty @member.subscribed))}}
|
||||||
<span class="gh-members-list-open-rate-mobile">{{@member.subscribed}}</span>
|
<span class="gh-members-list-open-rate-mobile">{{@member.subscribed}}</span>
|
||||||
|
@ -42,4 +38,20 @@
|
||||||
<span class="midlightgrey">-</span>
|
<span class="midlightgrey">-</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
|
{{else if (eq @filterColumn 'subscriptions.status')}}
|
||||||
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
|
{{#if (not (is-empty @member.subscriptions?.[0]?.status))}}
|
||||||
|
<span class="gh-members-list-open-rate-mobile">{{@member.subscriptions?.[0]?.status}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="midlightgrey">-</span>
|
||||||
|
{{/if}}
|
||||||
|
</LinkTo>
|
||||||
|
{{else if (eq @filterColumn 'subscriptions.plan_interval')}}
|
||||||
|
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||||
|
{{#if (not (is-empty @member.subscriptions?.[0]?.plan?.interval))}}
|
||||||
|
<span class="gh-members-list-open-rate-mobile">{{@member.subscriptions?.[0]?.plan?.interval}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="midlightgrey">-</span>
|
||||||
|
{{/if}}
|
||||||
|
</LinkTo>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -35,8 +35,8 @@
|
||||||
grid-column-gap: 8px;
|
grid-column-gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-filter-builder .gh-input,
|
.gh-filter-builder .gh-input,
|
||||||
.gh-filter-builder .gh-select,
|
.gh-filter-builder .gh-select,
|
||||||
.gh-filter-builder select {
|
.gh-filter-builder select {
|
||||||
height: 33px;
|
height: 33px;
|
||||||
font-size: 1.35rem;
|
font-size: 1.35rem;
|
||||||
|
@ -108,4 +108,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-filter-block .ember-power-select-multiple-trigger {
|
||||||
|
height: 33px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-filter-block .label-token {
|
||||||
|
margin: 2px !important;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue