diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 98fa8ce..f7c9545 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,7 @@ import PostDetailPage from '@/pages/posts/PostDetailPage' import PostHistoryPage from '@/pages/posts/PostHistoryPage' import PostListPage from '@/pages/posts/PostListPage' import PostNewPage from '@/pages/posts/PostNewPage' +import PostSearchPage from '@/pages/posts/PostSearchPage' import ServiceUnavailable from '@/pages/ServiceUnavailable' import SettingPage from '@/pages/users/SettingPage' import WikiDetailPage from '@/pages/wiki/WikiDetailPage' @@ -42,6 +43,7 @@ const RouteTransitionWrapper = ({ user, setUser }: { }/> }/> }/> + }/> }/> }/> }/> diff --git a/frontend/src/components/TopNav.tsx b/frontend/src/components/TopNav.tsx index 144f517..06c30cf 100644 --- a/frontend/src/components/TopNav.tsx +++ b/frontend/src/components/TopNav.tsx @@ -69,8 +69,9 @@ export default (({ user }: Props) => { const menu: Menu = [ { name: '広場', to: '/posts', subMenu: [ { name: '一覧', to: '/posts' }, + { name: '検索', to: '/posts/search' }, { name: '投稿追加', to: '/posts/new' }, - { name: '耕作履歴', to: '/posts/changes' }, + { name: '履歴', to: '/posts/changes' }, { name: 'ヘルプ', to: '/wiki/ヘルプ:広場' }] }, { name: 'タグ', to: '/tags', subMenu: [ { name: 'タグ一覧', to: '/tags', visible: false }, diff --git a/frontend/src/lib/prefetchers.ts b/frontend/src/lib/prefetchers.ts index d61291e..956f250 100644 --- a/frontend/src/lib/prefetchers.ts +++ b/frontend/src/lib/prefetchers.ts @@ -104,7 +104,7 @@ const prefetchPostChanges: Prefetcher = async (qc, url) => { export const routePrefetchers: { test: (u: URL) => boolean; run: Prefetcher }[] = [ { test: u => u.pathname === '/' || u.pathname === '/posts', run: prefetchPostsIndex }, - { test: u => (!(['/posts/new', '/posts/changes'].includes (u.pathname)) + { test: u => (!(['/posts/new', '/posts/changes', '/posts/search'].includes (u.pathname)) && Boolean (mPost (u.pathname))), run: prefetchPostShow }, { test: u => u.pathname === '/posts/changes', run: prefetchPostChanges }, diff --git a/frontend/src/pages/posts/PostSearchPage.tsx b/frontend/src/pages/posts/PostSearchPage.tsx new file mode 100644 index 0000000..cb5067e --- /dev/null +++ b/frontend/src/pages/posts/PostSearchPage.tsx @@ -0,0 +1,165 @@ +import { useState } from 'react' +import { Helmet } from 'react-helmet-async' +import { useLocation } from 'react-router-dom' + +import DateTimeField from '@/components/common/DateTimeField' +import Label from '@/components/common/Label' +import SectionTitle from '@/components/common/SectionTitle' +import MainArea from '@/components/layout/MainArea' +import { SITE_TITLE } from '@/config' +import { apiGet } from '@/lib/api' + +import type { FC, FormEvent } from 'react' + +import type { Post } from '@/types' + + +export default (() => { + const [createdFrom, setCreatedFrom] = useState (null) + const [createdTo, setCreatedTo] = useState (null) + const [matchType, setMatchType] = useState<'all' | 'any'> ('all') + const [originalCreatedFrom, setOriginalCreatedFrom] = useState (null) + const [originalCreatedTo, setOriginalCreatedTo] = useState (null) + const [tagsStr, setTagsStr] = useState ('') + const [title, setTitle] = useState ('') + const [updatedFrom, setUpdatedFrom] = useState (null) + const [updatedTo, setUpdatedTo] = useState (null) + const [url, setURL] = useState ('') + const [results, setResults] = useState ([]) + + const location = useLocation () + const query = new URLSearchParams (location.search) + const page = Number (query.get ('page') ?? 1) + const limit = Number (query.get ('limit') ?? 20) + + const search = async () => { + const tags = tagsStr.split (' ').filter (e => e !== '') + setResults (await apiGet ('/posts', { params: { + url, title, tags: tags.join (' '), match: matchType, + created_from: createdFrom, created_to: createdTo, + updated_from: updatedFrom, updated_to: updatedTo, + original_created_from: originalCreatedFrom, + original_created_to: originalCreatedTo, + page, limit } })) + } + + const handleSearch = (e: FormEvent) => { + e.preventDefault () + search () + } + + return ( + + + 広場検索 | {SITE_TITLE} + + + + 広場検索 + + {/* URL */} + + URL + setURL (e.target.value)} + className="w-full border p-2 rounded"/> + + + {/* タイトル */} + + タイトル + setTitle (e.target.value)} + className="w-full border p-2 rounded"/> + + + {/* タグ */} + + タグ + setTagsStr (e.target.value)} + className="w-full border p-2 rounded"/> + + 検索区分: + + setMatchType ('all')}/> + AND + + + setMatchType ('any')}/> + OR + + + + + {/* 投稿日時 */} + + 投稿日時 + + 〜 + + + + {/* 更新日時 */} + + 更新日時 + + 〜 + + + + {/* オリジナルの投稿日時 */} + + オリジナルの投稿日時 + + 〜 + + + + {/* 検索 */} + + + 検索 + + + + + + + + + + + + + + ) +}) satisfies FC