このコミットが含まれているのは:
2026-06-26 00:21:33 +09:00
コミット 8304909c8c
26個のファイルの変更1251行の追加33行の削除
+7
ファイルの表示
@@ -8,6 +8,7 @@ import WikiBody from '@/components/WikiBody'
import FieldError from '@/components/common/FieldError'
import FormField from '@/components/common/FormField'
import PageTitle from '@/components/common/PageTitle'
import PrefetchLink from '@/components/PrefetchLink'
import TabGroup, { Tab } from '@/components/common/TabGroup'
import TagInput from '@/components/common/TagInput'
import MainArea from '@/components/layout/MainArea'
@@ -118,6 +119,12 @@ const MaterialDetailPage: FC = () => {
: materialTitle}
</PageTitle>
<PrefetchLink
to={`/materials/changes?material_id=${ material.id }`}
className="text-sm text-sky-700 underline underline-offset-2 dark:text-sky-300">
</PrefetchLink>
{(material.file && material.contentType) && (
(/image\/.*/.test (material.contentType) && (
<img src={material.file} alt={material.tag?.name || undefined}/>))
+213
ファイルの表示
@@ -0,0 +1,213 @@
import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useLocation, useNavigate } from 'react-router-dom'
import PrefetchLink from '@/components/PrefetchLink'
import FormField from '@/components/common/FormField'
import PageTitle from '@/components/common/PageTitle'
import Pagination from '@/components/common/Pagination'
import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config'
import { fetchMaterialChanges } from '@/lib/materials'
import { materialsKeys } from '@/lib/queryKeys'
import { dateString, inputClass } from '@/lib/utils'
import type { FC, FormEvent } from 'react'
const MaterialHistoryPage: FC = () => {
const location = useLocation ()
const navigate = useNavigate ()
const query = new URLSearchParams (location.search)
const materialId = query.get ('material_id') ?? ''
const tag = query.get ('tag') ?? ''
const eventType = query.get ('event_type') ?? ''
const page = Number (query.get ('page') ?? 1)
const limit = Number (query.get ('limit') ?? 20)
const [materialIdInput, setMaterialIdInput] = useState (materialId)
const [tagInput, setTagInput] = useState (tag)
const [eventTypeInput, setEventTypeInput] = useState (eventType)
useEffect (() => {
setMaterialIdInput (materialId)
setTagInput (tag)
setEventTypeInput (eventType)
}, [eventType, materialId, tag])
const { data, isLoading, isError } = useQuery ({
queryKey: materialsKeys.changes ({
...(materialId && { materialId }),
...(tag && { tag }),
...(eventType && { eventType }),
page,
limit }),
queryFn: () => fetchMaterialChanges ({
...(materialId && { materialId }),
...(tag && { tag }),
...(eventType && { eventType }),
page,
limit }) })
const versions = data?.versions ?? []
const totalPages = data ? Math.ceil (data.count / limit) : 0
useEffect (() => {
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search])
const handleSearch = (event: FormEvent) => {
event.preventDefault ()
const qs = new URLSearchParams ()
if (materialIdInput.trim ())
qs.set ('material_id', materialIdInput.trim ())
if (tagInput.trim ())
qs.set ('tag', tagInput.trim ())
if (eventTypeInput.trim ())
qs.set ('event_type', eventTypeInput.trim ())
qs.set ('page', '1')
qs.set ('limit', String (limit))
navigate (`/materials/changes?${ qs.toString () }`)
}
return (
<MainArea>
<Helmet>
<title>{`素材履歴 | ${ SITE_TITLE }`}</title>
</Helmet>
<div className="space-y-5">
<div className="flex flex-wrap items-center justify-between gap-3">
<PageTitle></PageTitle>
<PrefetchLink
to="/materials"
className="text-sm text-sky-700 underline underline-offset-2 dark:text-sky-300">
</PrefetchLink>
</div>
<form
onSubmit={handleSearch}
className="max-w-3xl rounded-lg border border-stone-200 bg-white p-4
text-stone-900 dark:border-stone-700 dark:bg-stone-900
dark:text-stone-100">
<div className="space-y-3">
<FormField label="素材 ID">
{({ invalid }) => (
<input
type="text"
value={materialIdInput}
onChange={e => setMaterialIdInput (e.target.value)}
className={inputClass (invalid)}/>)}
</FormField>
<FormField label="タグ名">
{({ invalid }) => (
<input
type="text"
value={tagInput}
onChange={e => setTagInput (e.target.value)}
className={inputClass (invalid)}/>)}
</FormField>
<FormField label="イベント">
{({ invalid }) => (
<select
value={eventTypeInput}
onChange={e => setEventTypeInput (e.target.value)}
className={inputClass (invalid)}>
<option value=""></option>
<option value="create">create</option>
<option value="update">update</option>
<option value="discard">discard</option>
<option value="restore">restore</option>
</select>)}
</FormField>
</div>
<button
type="submit"
className="mt-4 rounded-full border border-stone-300 bg-white px-4 py-2
text-sm text-stone-900 hover:bg-stone-100 dark:border-stone-700
dark:bg-stone-900 dark:text-stone-100 dark:hover:bg-stone-800">
</button>
</form>
{isLoading
? 'Loading...'
: isError
? (
<p className="text-red-600 dark:text-red-300">
</p>)
: (
<>
<div className="overflow-x-auto">
<table className="w-full min-w-[1200px] table-fixed border-collapse">
<colgroup>
<col className="w-48"/>
<col className="w-32"/>
<col className="w-28"/>
<col className="w-24"/>
<col className="w-64"/>
<col className="w-64"/>
<col className="w-80"/>
<col className="w-80"/>
<col className="w-48"/>
</colgroup>
<thead className="border-b-2 border-black dark:border-white">
<tr>
<th className="p-2 text-left"></th>
<th className="p-2 text-left">event_type</th>
<th className="p-2 text-left"> ID</th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left">URL</th>
<th className="p-2 text-left">export_path</th>
<th className="p-2 text-left"></th>
</tr>
</thead>
<tbody>
{versions.map (version => (
<tr
key={version.id}
className="even:bg-gray-100 dark:even:bg-gray-700">
<td className="p-2">{dateString (version.createdAt)}</td>
<td className="p-2">{version.eventType}</td>
<td className="p-2">
<PrefetchLink to={`/materials/${ version.materialId }`}>
#{version.materialId}
</PrefetchLink>
</td>
<td className="p-2">{version.versionNo}</td>
<td className="p-2 break-all">{version.tagName}</td>
<td className="p-2 break-all">{version.fileFilename}</td>
<td className="p-2 break-all">{version.url}</td>
<td className="p-2 break-all">
{version.exportPathsJson.legacy_drive}
</td>
<td className="p-2">
{version.createdByUser
? version.createdByUser.name || `#${ version.createdByUser.id }`
: 'bot 操作'}
</td>
</tr>))}
</tbody>
</table>
</div>
<Pagination page={page} totalPages={totalPages}/>
</>)}
</div>
</MainArea>)
}
export default MaterialHistoryPage
+7
ファイルの表示
@@ -284,6 +284,13 @@ const MaterialListPage: FC = () => {
dark:bg-stone-900 dark:text-stone-100 dark:hover:bg-stone-800">
</PrefetchLink>
<PrefetchLink
to="/materials/changes"
className="rounded-full border border-stone-300 bg-white px-4 py-2 text-sm
text-stone-900 hover:bg-stone-100 dark:border-stone-700
dark:bg-stone-900 dark:text-stone-100 dark:hover:bg-stone-800">
</PrefetchLink>
<a
href={`${ API_BASE_URL }/materials/download.zip?profile=legacy_drive`}
target="_blank"
+3 -1
ファイルの表示
@@ -23,8 +23,10 @@ import type { MaterialSyncSuppressionSourceKind } from '@/types'
const SOURCE_KIND_LABELS: Record<MaterialSyncSuppressionSourceKind, string> = {
uri: 'URI',
google_drive_path: 'Google Drive path',
google_drive_path_prefix: 'Google Drive path prefix',
google_drive_file: 'Google Drive file ID',
legacy_drive_path: 'Legacy Drive path'}
legacy_drive_path: 'Legacy Drive path',
legacy_drive_path_prefix: 'Legacy Drive path prefix'}
const REASONS = [
'copyright_high_risk',