タグ詳細 (#318) (#328)

#318

#318

#318

#318

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #328
このコミットはPull リクエスト #328 でマージされました.
このコミットが含まれているのは:
2026-04-23 00:06:49 +09:00
コミット 43cd38a216
11個のファイルの変更642行の追加49行の削除
+158
ファイルの表示
@@ -0,0 +1,158 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import TagLink from '@/components/TagLink'
import Label from '@/components/common/Label'
import PageTitle from '@/components/common/PageTitle'
import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast'
import { CATEGORIES, CATEGORY_NAMES } from '@/consts'
import { apiPut } from '@/lib/api'
import { postsKeys, tagsKeys } from '@/lib/queryKeys'
import { fetchTag } from '@/lib/tags'
import { cn } from '@/lib/utils'
import type { FC, FormEvent } from 'react'
import type { Category, Tag } from '@/types'
export default (() => {
const { id } = useParams ()
const tagId = String (id ?? '')
const tagKey = tagsKeys.show (tagId)
const { data: tag, isLoading: loading } = useQuery ({
queryKey: tagKey,
queryFn: () => fetchTag (tagId) })
const [name, setName] = useState ('')
const [category, setCategory] = useState<Category> ('general')
const [aliases, setAliases] = useState ('')
const [parentTags, setParentTags] = useState ('')
const [disabled, setDisabled] = useState (true)
const qc = useQueryClient ()
const handleSubmit = async (e: FormEvent) => {
e.preventDefault ()
const formData = new FormData
formData.append ('name', name)
formData.append ('category', category)
formData.append ('aliases', aliases)
formData.append ('parent_tags', parentTags)
try
{
const data = await apiPut<Tag> (`/tags/${ id }`, formData)
setName (data.name)
setCategory (data.category as Category)
setAliases (data.aliases.join (' '))
setParentTags (data.parents.map (t => t.name).join (' '))
qc.invalidateQueries ({ queryKey: postsKeys.root })
qc.invalidateQueries ({ queryKey: tagsKeys.root })
toast ({ description: '更新しました.' })
}
catch
{
toast ({ description: '更新に失敗しました.' })
}
}
useEffect (() => {
if (!(tag))
{
setDisabled (true)
return
}
setName (tag.name)
setCategory (tag.category as Category)
setAliases (tag.aliases.join (' '))
setParentTags (tag.parents.map (t => t.name).join (' '))
setDisabled (tag.category === 'nico')
}, [tag])
return (
<MainArea>
{(loading || !(tag)) ? 'Loading...' : (
<div className="max-w-xl">
<PageTitle>
<TagLink
tag={tag}
withWiki={false}
withCount={false}/>
</PageTitle>
<form onSubmit={handleSubmit} className="my-4 space-y-2">
{/* 名称 */}
<div>
<Label></Label>
{/* TODO: 補完に対応させる */}
<input
type="text"
disabled={disabled}
value={name}
onChange={e => setName (e.target.value)}
className="w-full border p-2 rounded"/>
</div>
{/* カテゴリ */}
<div>
<Label></Label>
<select
disabled={disabled}
value={category ?? ''}
onChange={e => setCategory(e.target.value as Category)}
className="w-full border p-2 rounded">
{CATEGORIES.filter (cat => tag.category === 'nico' || cat !== 'nico')
.map (cat => (
<option key={cat} value={cat}>
{CATEGORY_NAMES[cat]}
</option>))}
</select>
</div>
{/* 別名 */}
<div>
<Label></Label>
{/* TODO: 補完に対応させる */}
<input
type="text"
disabled={disabled}
value={aliases}
onChange={e => setAliases (e.target.value)}
className="w-full border p-2 rounded"/>
</div>
{/* 上位タグ */}
<div>
<Label></Label>
{/* TODO: 補完に対応させる */}
<input
type="text"
disabled={disabled}
value={parentTags}
onChange={e => setParentTags (e.target.value)}
className="w-full border p-2 rounded"/>
</div>
<div className="py-3">
<button
type="submit"
disabled={disabled}
className={cn ('px-4 py-2 rounded',
(disabled
? 'text-gray-300 bg-gray-500'
: 'text-white bg-blue-500'))}>
</button>
</div>
</form>
</div>)}
</MainArea>)
}) satisfies FC
+30 -13
ファイルの表示
@@ -205,13 +205,15 @@ export default (() => {
{loading ? 'Loading...' : (results.length > 0 ? (
<div className="mt-4">
<div className="overflow-x-auto">
<table className="w-full min-w-[1200px] table-fixed border-collapse">
<table className="w-full min-w-[2000px] table-fixed border-collapse">
<colgroup>
<col className="w-72"/>
<col className="w-48"/>
<col className="w-16"/>
<col className="w-44"/>
<col className="w-44"/>
<col className="w-48"/>
<col className="w-72"/>
<col className="w-48"/>
<col className="w-56"/>
<col className="w-56"/>
<col className="w-16"/>
</colgroup>
@@ -224,13 +226,6 @@ export default (() => {
currentOrder={order}
defaultDirection={defaultDirection}/>
</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader<FetchTagsOrderField>
by="category"
label="カテゴリ"
currentOrder={order}
defaultDirection={defaultDirection}/>
</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader<FetchTagsOrderField>
by="post_count"
@@ -238,6 +233,15 @@ export default (() => {
currentOrder={order}
defaultDirection={defaultDirection}/>
</th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader<FetchTagsOrderField>
by="category"
label="カテゴリ"
currentOrder={order}
defaultDirection={defaultDirection}/>
</th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap">
<SortHeader<FetchTagsOrderField>
by="created_at"
@@ -260,10 +264,23 @@ export default (() => {
{results.map (row => (
<tr key={row.id} className="even:bg-gray-100 dark:even:bg-gray-700">
<td className="p-2">
<TagLink tag={row} withCount={false}/>
<TagLink
tag={row}
to={`/tags/${ encodeURIComponent (row.id) }`}
withCount={false}/>
</td>
<td className="p-2">{CATEGORY_NAMES[row.category]}</td>
<td className="p-2 text-right">{row.postCount}</td>
<td className="p-2">{CATEGORY_NAMES[row.category]}</td>
<td className="p-2">{row.aliases.join (' ')}</td>
<td className="p-2">
{row.parents.map (t => (
<span key={t.id} className="mr-2">
<TagLink
tag={t}
withWiki={false}
withCount={false}/>
</span>))}
</td>
<td className="p-2">{dateString (row.createdAt)}</td>
<td className="p-2">{dateString (row.updatedAt)}</td>
<td className="p-2">
+9 -7
ファイルの表示
@@ -114,13 +114,15 @@ export default () => {
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
</h1>
{loading ? <div>Loading...</div> : <WikiBody title={title} body={wikiPage?.body}/>}
</article>
{(!(version) && posts.length > 0) && (
<TabGroup>
<Tab name="広場">
<PostList posts={posts}/>
</Tab>
</TabGroup>)}
{(!(version) && posts.length > 0) && (
<div className="not-prose">
<TabGroup>
<Tab name="広場">
<PostList posts={posts}/>
</Tab>
</TabGroup>
</div>)}
</article>
</MainArea>)
}