mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 19:57:52 +01:00
Improve post list filter by status (#267)
This commit is contained in:
committed by
GitHub
parent
30b7b0f5f4
commit
a7d67652bf
@@ -48,7 +48,7 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
filter: brightness(90%);
|
filter: brightness(85%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.postStatusListItemSelected {
|
||||||
|
filter: brightness(85%);
|
||||||
|
}
|
||||||
|
|
||||||
.resetFilter {
|
.resetFilter {
|
||||||
@extend
|
@extend
|
||||||
.flex-grow-0,
|
.flex-grow-0,
|
||||||
@@ -67,6 +71,7 @@
|
|||||||
|
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.postList {
|
.postList {
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ class PostsController < ApplicationController
|
|||||||
.left_outer_joins(:likes)
|
.left_outer_joins(:likes)
|
||||||
.left_outer_joins(:comments)
|
.left_outer_joins(:comments)
|
||||||
.group('posts.id')
|
.group('posts.id')
|
||||||
.where(filter_params)
|
.where(board_id: params[:board_id] || Board.first.id)
|
||||||
.search_by_name_or_description(params[:search])
|
.search_by_name_or_description(params[:search])
|
||||||
.order('hotness DESC')
|
.order('hotness DESC')
|
||||||
.page(params[:page])
|
.page(params[:page])
|
||||||
|
|
||||||
|
# apply post status filter if present
|
||||||
|
posts = posts.where(post_status_id: params[:post_status_ids].map { |id| id == "0" ? nil : id }) if params[:post_status_ids].present?
|
||||||
|
|
||||||
render json: posts
|
render json: posts
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -108,15 +111,6 @@ class PostsController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def filter_params
|
|
||||||
defaults = Board.first ? { board_id: Board.first.id } : {}
|
|
||||||
|
|
||||||
params
|
|
||||||
.permit(:board_id, :post_status_id, :page, :search)
|
|
||||||
.with_defaults(defaults)
|
|
||||||
.except(:page, :search)
|
|
||||||
end
|
|
||||||
|
|
||||||
def post_create_params
|
def post_create_params
|
||||||
params
|
params
|
||||||
.require(:post)
|
.require(:post)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const requestPosts = (
|
|||||||
boardId: number,
|
boardId: number,
|
||||||
page: number,
|
page: number,
|
||||||
searchQuery: string,
|
searchQuery: string,
|
||||||
postStatusId: number,
|
postStatusIds: Array<number>,
|
||||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||||
dispatch(postsRequestStart());
|
dispatch(postsRequestStart());
|
||||||
|
|
||||||
@@ -57,7 +57,14 @@ export const requestPosts = (
|
|||||||
params += `page=${page}`;
|
params += `page=${page}`;
|
||||||
params += `&board_id=${boardId}`;
|
params += `&board_id=${boardId}`;
|
||||||
if (searchQuery) params += `&search=${searchQuery}`;
|
if (searchQuery) params += `&search=${searchQuery}`;
|
||||||
if (postStatusId) params += `&post_status_id=${postStatusId}`;
|
if (postStatusIds) {
|
||||||
|
params += '&';
|
||||||
|
|
||||||
|
for (let i = 0; i < postStatusIds.length; i++) {
|
||||||
|
params += `post_status_ids[]=${postStatusIds[i]}`;
|
||||||
|
if (i !== postStatusIds.length-1) params += '&';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/posts?${params}`);
|
const response = await fetch(`/posts?${params}`);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface Props {
|
|||||||
boardId: number,
|
boardId: number,
|
||||||
page?: number,
|
page?: number,
|
||||||
searchQuery?: string,
|
searchQuery?: string,
|
||||||
postStatusId?: number,
|
postStatusIds?: Array<number>,
|
||||||
): void;
|
): void;
|
||||||
requestPostStatuses(): void;
|
requestPostStatuses(): void;
|
||||||
handleSearchFilterChange(searchQuery: string): void;
|
handleSearchFilterChange(searchQuery: string): void;
|
||||||
@@ -44,21 +44,21 @@ class BoardP extends React.Component<Props> {
|
|||||||
const { searchQuery } = this.props.posts.filters;
|
const { searchQuery } = this.props.posts.filters;
|
||||||
const prevSearchQuery = prevProps.posts.filters.searchQuery;
|
const prevSearchQuery = prevProps.posts.filters.searchQuery;
|
||||||
|
|
||||||
const { postStatusId } = this.props.posts.filters;
|
const { postStatusIds } = this.props.posts.filters;
|
||||||
const prevPostStatusId = prevProps.posts.filters.postStatusId;
|
const prevPostStatusIds = prevProps.posts.filters.postStatusIds;
|
||||||
|
|
||||||
// search filter changed
|
// search filter changed
|
||||||
if (searchQuery !== prevSearchQuery) {
|
if (searchQuery !== prevSearchQuery) {
|
||||||
if (this.searchFilterTimeoutId) clearInterval(this.searchFilterTimeoutId);
|
if (this.searchFilterTimeoutId) clearInterval(this.searchFilterTimeoutId);
|
||||||
|
|
||||||
this.searchFilterTimeoutId = setTimeout(() => (
|
this.searchFilterTimeoutId = setTimeout(() => (
|
||||||
this.props.requestPosts(this.props.board.id, 1, searchQuery, postStatusId)
|
this.props.requestPosts(this.props.board.id, 1, searchQuery, postStatusIds)
|
||||||
), 500);
|
), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// post status filter changed
|
// post status filter changed
|
||||||
if (postStatusId !== prevPostStatusId) {
|
if (postStatusIds.length !== prevPostStatusIds.length) {
|
||||||
this.props.requestPosts(this.props.board.id, 1, searchQuery, postStatusId);
|
this.props.requestPosts(this.props.board.id, 1, searchQuery, postStatusIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class BoardP extends React.Component<Props> {
|
|||||||
areLoading={postStatuses.areLoading}
|
areLoading={postStatuses.areLoading}
|
||||||
error={postStatuses.error}
|
error={postStatuses.error}
|
||||||
|
|
||||||
currentFilter={filters.postStatusId}
|
currentFilter={filters.postStatusIds}
|
||||||
handleFilterClick={handlePostStatusFilterChange}
|
handleFilterClick={handlePostStatusFilterChange}
|
||||||
/>
|
/>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
@@ -113,7 +113,7 @@ class BoardP extends React.Component<Props> {
|
|||||||
posts.areLoading ?
|
posts.areLoading ?
|
||||||
null
|
null
|
||||||
:
|
:
|
||||||
requestPosts(board.id, posts.page + 1, filters.searchQuery, filters.postStatusId)
|
requestPosts(board.id, posts.page + 1, filters.searchQuery, filters.postStatusIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface Props {
|
|||||||
error: string;
|
error: string;
|
||||||
|
|
||||||
handleFilterClick(postStatusId: number): void;
|
handleFilterClick(postStatusId: number): void;
|
||||||
currentFilter: number;
|
currentFilter: Array<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostStatusFilter = ({
|
const PostStatusFilter = ({
|
||||||
@@ -33,13 +33,19 @@ const PostStatusFilter = ({
|
|||||||
color={postStatus.color}
|
color={postStatus.color}
|
||||||
|
|
||||||
handleClick={() => handleFilterClick(postStatus.id)}
|
handleClick={() => handleFilterClick(postStatus.id)}
|
||||||
isCurrentFilter={postStatus.id === currentFilter}
|
isCurrentFilter={currentFilter.includes(postStatus.id)}
|
||||||
handleResetFilter={() => handleFilterClick(0)}
|
|
||||||
|
|
||||||
key={i}
|
key={i}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
<PostStatusListItem
|
||||||
|
name={I18n.t('common.no_status')}
|
||||||
|
color='black'
|
||||||
|
|
||||||
|
handleClick={() => handleFilterClick(0)}
|
||||||
|
isCurrentFilter={currentFilter.includes(0)}
|
||||||
|
/>
|
||||||
{ areLoading ? <Spinner /> : null }
|
{ areLoading ? <Spinner /> : null }
|
||||||
{ error ? <DangerText>{error}</DangerText> : null }
|
{ error ? <DangerText>{error}</DangerText> : null }
|
||||||
</SidebarBox>
|
</SidebarBox>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import PostStatusLabel from '../common/PostStatusLabel';
|
import PostStatusLabel from '../common/PostStatusLabel';
|
||||||
import Button from '../common/Button';
|
import Button from '../common/Button';
|
||||||
|
import { CancelIcon } from '../common/Icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -9,7 +10,6 @@ interface Props {
|
|||||||
|
|
||||||
handleClick(): void;
|
handleClick(): void;
|
||||||
isCurrentFilter: boolean;
|
isCurrentFilter: boolean;
|
||||||
handleResetFilter(): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostStatusListItem = ({
|
const PostStatusListItem = ({
|
||||||
@@ -17,19 +17,18 @@ const PostStatusListItem = ({
|
|||||||
color,
|
color,
|
||||||
handleClick,
|
handleClick,
|
||||||
isCurrentFilter,
|
isCurrentFilter,
|
||||||
handleResetFilter,
|
|
||||||
}: Props) => (
|
}: Props) => (
|
||||||
<div className={
|
<div className={
|
||||||
"postStatusListItemContainer " + `postStatus${name.replace(/ /g, '')}`
|
"postStatusListItemContainer " + `postStatus${name.replace(/ /g, '')}`
|
||||||
}>
|
}>
|
||||||
<a onClick={handleClick} className="postStatusListItemLink">
|
<a onClick={handleClick} className="postStatusListItemLink">
|
||||||
<div className="postStatusListItem">
|
<div className={`postStatusListItem${isCurrentFilter ? ' postStatusListItemSelected' : ''}`}>
|
||||||
<PostStatusLabel name={name} color={color} />
|
<PostStatusLabel name={name} color={color} />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{
|
{
|
||||||
isCurrentFilter ?
|
isCurrentFilter ?
|
||||||
<Button onClick={handleResetFilter} className="resetFilter" outline>
|
<Button onClick={handleClick} className="resetFilter" outline>
|
||||||
X
|
X
|
||||||
</Button>
|
</Button>
|
||||||
:
|
:
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const mapStateToProps = (state: State) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => ({
|
const mapDispatchToProps = (dispatch: any) => ({
|
||||||
requestPosts(boardId: number, page: number = 1, searchQuery: string = '', postStatusId: number = null) {
|
requestPosts(boardId: number, page: number = 1, searchQuery: string = '', postStatusIds: Array<number> = null) {
|
||||||
dispatch(requestPosts(boardId, page, searchQuery, postStatusId));
|
dispatch(requestPosts(boardId, page, searchQuery, postStatusIds));
|
||||||
},
|
},
|
||||||
|
|
||||||
requestPostStatuses() {
|
requestPostStatuses() {
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
|
|
||||||
export interface FiltersState {
|
export interface FiltersState {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
postStatusId: number;
|
postStatusIds: Array<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: FiltersState = {
|
const initialState: FiltersState = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
postStatusId: null,
|
postStatusIds: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtersReducer = (
|
const filtersReducer = (
|
||||||
@@ -28,7 +28,9 @@ const filtersReducer = (
|
|||||||
case SET_POST_STATUS_FILTER:
|
case SET_POST_STATUS_FILTER:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
postStatusId: action.postStatusId,
|
postStatusIds: state.postStatusIds.includes(action.postStatusId)
|
||||||
|
? state.postStatusIds.filter(id => id !== action.postStatusId)
|
||||||
|
: [...state.postStatusIds, action.postStatusId],
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -131,14 +131,24 @@ feature 'board', type: :system, js: true do
|
|||||||
expect(page).to have_no_content(/#{post2.title}/i)
|
expect(page).to have_no_content(/#{post2.title}/i)
|
||||||
expect(page).to have_no_content(/#{post3.title}/i)
|
expect(page).to have_no_content(/#{post3.title}/i)
|
||||||
|
|
||||||
# you can also clear the filter
|
# you can also filter by multiple statuses
|
||||||
within sidebar do
|
within sidebar do
|
||||||
find(reset_filter).click
|
selector = ".postStatus#{post_status2.name.gsub(' ', '')}"
|
||||||
|
find(selector).click
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(page).to have_content(/#{post1.title}/i)
|
expect(page).to have_content(/#{post1.title}/i)
|
||||||
expect(page).to have_content(/#{post2.title}/i)
|
expect(page).to have_content(/#{post2.title}/i)
|
||||||
expect(page).to have_content(/#{post3.title}/i)
|
expect(page).to have_no_content(/#{post3.title}/i)
|
||||||
|
|
||||||
|
# you can also clear the filter
|
||||||
|
within sidebar do
|
||||||
|
find(reset_filter, match: :first).click
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to have_no_content(/#{post1.title}/i)
|
||||||
|
expect(page).to have_content(/#{post2.title}/i)
|
||||||
|
expect(page).to have_no_content(/#{post3.title}/i)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'enables users to search posts by title and description' do
|
it 'enables users to search posts by title and description' do
|
||||||
|
|||||||
Reference in New Issue
Block a user