diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index 34cb37d..c89931f 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -9,6 +9,19 @@ class PostsController < ApplicationController created_between = params[:created_from].presence, params[:created_to].presence updated_between = params[:updated_from].presence, params[:updated_to].presence + order = params[:order].to_s.split(':', 2).map(&:strip) + unless order[0].in?(['title', 'url', 'original_created_at', 'created_at', 'updated_at']) + order[0] = 'original_created_at' + end + unless order[1].in?(['asc', 'desc']) + order[1] = + if order[0].in?(['title', 'url']) + 'asc' + else + 'desc' + end + end + page = (params[:page].presence || 1).to_i limit = (params[:limit].presence || 20).to_i @@ -36,11 +49,16 @@ class PostsController < ApplicationController q = q.where('posts.updated_at <= ?', updated_between[1]) if updated_between[1] sort_sql = - 'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' + - 'posts.original_created_from,' + - 'posts.created_at)' - posts = q.select("posts.*, #{ sort_sql } AS sort_ts") - .order(Arel.sql("#{ sort_sql } DESC")) + if order[0] == 'original_created_at' + 'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' + + 'posts.original_created_from,' + + 'posts.created_at) ' + + order[1] + else + "posts.#{ order[0] } #{ order[1] }" + end + posts = q.select('posts.*') + .order(Arel.sql("#{ sort_sql }")) .limit(limit).offset(offset).to_a render json: { posts: posts.map { |post| diff --git a/frontend/src/lib/prefetchers.ts b/frontend/src/lib/prefetchers.ts index a0380e0..56361c7 100644 --- a/frontend/src/lib/prefetchers.ts +++ b/frontend/src/lib/prefetchers.ts @@ -8,6 +8,8 @@ import { fetchWikiPage, fetchWikiPageByTitle, fetchWikiPages } from '@/lib/wiki' +import type { FetchPostsOrder } from '@/types' + type Prefetcher = (qc: QueryClient, url: URL) => Promise const mPost = match<{ id: string }> ('/posts/:id') @@ -79,17 +81,19 @@ const prefetchPostsIndex: Prefetcher = async (qc, url) => { const m: 'all' | 'any' = url.searchParams.get ('match') === 'any' ? 'any' : 'all' const page = Number (url.searchParams.get ('page') || 1) const limit = Number (url.searchParams.get ('limit') || 20) + const order = url.searchParams.get ('order') as FetchPostsOrder | null const keys = { tags, match: m, page, limit, ...(qURL && { url: qURL }), ...(title && { title }), - ...(originalCreatedFrom && { original_created_from: originalCreatedFrom }), - ...(originalCreatedTo && { original_created_to: originalCreatedTo }), - ...(createdFrom && { created_from: createdFrom }), - ...(createdTo && { created_to: createdTo }), - ...(updatedFrom && { updated_from: updatedFrom }), - ...(updatedTo && { updated_to: updatedTo }) } + ...(originalCreatedFrom && { originalCreatedFrom }), + ...(originalCreatedTo && { originalCreatedTo }), + ...(createdFrom && { createdFrom }), + ...(createdTo && { createdTo }), + ...(updatedFrom && { updatedFrom }), + ...(updatedTo && { updatedTo }), + ...(order && { order }) } await qc.prefetchQuery ({ queryKey: postsKeys.index (keys), diff --git a/frontend/src/pages/posts/PostSearchPage.tsx b/frontend/src/pages/posts/PostSearchPage.tsx index 461461f..58bf877 100644 --- a/frontend/src/pages/posts/PostSearchPage.tsx +++ b/frontend/src/pages/posts/PostSearchPage.tsx @@ -18,7 +18,10 @@ import { postsKeys } from '@/lib/queryKeys' import type { FC, ChangeEvent, FormEvent, KeyboardEvent } from 'react' -import type { FetchPostsOrder, FetchPostsParams, Tag } from '@/types' +import type { FetchPostsOrder, + FetchPostsOrderField, + FetchPostsParams, + Tag } from '@/types' const setIf = (qs: URLSearchParams, k: string, v: string | null) => { @@ -49,13 +52,12 @@ export default (() => { const qCreatedTo = query.get ('created_to') const qUpdatedFrom = query.get ('updated_from') const qUpdatedTo = query.get ('updated_to') - const qOrder = (query.get ('order') || 'original_created_at:desc') as FetchPostsOrder + const order = (query.get ('order') || 'original_created_at:desc') as FetchPostsOrder const [activeIndex, setActiveIndex] = useState (-1) const [createdFrom, setCreatedFrom] = useState (qCreatedFrom) const [createdTo, setCreatedTo] = useState (qCreatedTo) const [matchType, setMatchType] = useState (qMatch ?? 'all') - const [order, setOrder] = useState ('original_created_at:desc') const [originalCreatedFrom, setOriginalCreatedFrom] = useState (qOriginalCreatedFrom) const [originalCreatedTo, setOriginalCreatedTo] = useState (qOriginalCreatedTo) const [suggestions, setSuggestions] = useState ([]) @@ -76,7 +78,7 @@ export default (() => { ...(qCreatedTo && { createdTo: qCreatedTo }), ...(qUpdatedFrom && { updatedFrom: qUpdatedFrom }), ...(qUpdatedTo && { updatedTo: qUpdatedTo }), - ...(qOrder && { order: qOrder }) } + ...(order && { order }) } const { data, isLoading: loading } = useQuery ({ queryKey: postsKeys.index (keys), queryFn: () => fetchPosts (keys) }) @@ -94,11 +96,32 @@ export default (() => { setCreatedTo (qCreatedTo) setUpdatedFrom (qUpdatedFrom) setUpdatedTo (qUpdatedTo) - setOrder (qOrder) document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' }) }, [location.search]) + const SortHeader = ({ by, label }: { by: FetchPostsOrderField; label: string }) => { + const [fld, dir] = order.split (':') + + const qs = new URLSearchParams (location.search) + const nextDir = + (by === fld) + ? (dir === 'asc' ? 'desc' : 'asc') + : (['title', 'url'].includes (by) ? 'asc' : 'desc') + qs.set ('order', `${ by }:${ nextDir }`) + qs.set ('page', '1') + + return ( + + + {label} + {by === fld && (dir === 'asc' ? ' ▲' : ' ▼')} + + ) + } + // TODO: TagSearch からのコピペのため,共通化を考へる. const whenChanged = async (ev: ChangeEvent) => { setTagsStr (ev.target.value) @@ -305,7 +328,7 @@ export default (() => { - + @@ -313,12 +336,22 @@ export default (() => { 投稿 - タイトル - URL + + + + + + タグ - オリジナルの投稿日時 - 投稿日時 - 更新日時 + + + + + + + + +