このコミットが含まれているのは:
2026-06-23 22:05:11 +09:00
コミット 507ce1680e
25個のファイルの変更1148行の追加111行の削除
+69 -14
ファイルの表示
@@ -13,22 +13,23 @@ import MainArea from '@/components/layout/MainArea'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import { apiGet, apiPut } from '@/lib/api'
import { apiGet, apiPatch, apiPut } from '@/lib/api'
import { inputClass } from '@/lib/utils'
import { useValidationErrors } from '@/lib/useValidationErrors'
import type { FC } from 'react'
import type { Material, Tag } from '@/types'
import type { Material, Tag, User } from '@/types'
type MaterialWithTag = Material & { tag: Tag }
type MaterialFormField = 'tag' | 'file' | 'url'
type MaterialFormField = 'tag' | 'file' | 'url' | 'exportPaths'
const MaterialDetailPage: FC = () => {
const MaterialDetailPage: FC<{ user: User | null }> = ({ user }) => {
const { id } = useParams ()
const [exportPath, setExportPath] = useState ('')
const [file, setFile] = useState<File | null> (null)
const [filePreview, setFilePreview] = useState ('')
const [loading, setLoading] = useState (false)
@@ -49,6 +50,7 @@ const MaterialDetailPage: FC = () => {
formData.append ('file', file)
if (url.trim ())
formData.append ('url', url)
formData.append ('export_paths[legacy_drive]', exportPath)
try
{
@@ -68,6 +70,30 @@ const MaterialDetailPage: FC = () => {
}
}
const handleSuppress = async () => {
const reason = window.prompt ('抑止理由を入力してください。')
if (reason == null || reason.trim () === '')
return
if (!window.confirm ('素材ファイルを抑止します。表示と ZIP export から除外されます。'))
return
try
{
const data = await apiPatch<Material> (
`/materials/${ id }/suppress_file`,
{ reason },
)
setMaterial (data)
setFile (null)
setFilePreview ('')
toast ({ title: '抑止しました' })
}
catch
{
toast ({ title: '抑止に失敗しました' })
}
}
useEffect (() => {
if (!(id))
return
@@ -82,11 +108,10 @@ const MaterialDetailPage: FC = () => {
if (data.file && data.contentType)
{
setFilePreview (data.file)
setFile (new File ([await (await fetch (data.file)).blob ()],
data.file,
{ type: data.contentType }))
setFile (null)
}
setURL (data.url ?? '')
setExportPath (data.exportPaths.legacyDrive ?? '')
}
finally
{
@@ -111,7 +136,14 @@ const MaterialDetailPage: FC = () => {
withCount={false}/>
</PageTitle>
{(material.file && material.contentType) && (
{material.fileSuppressedAt && (
<div className="mb-4 rounded border border-red-300 bg-red-50 p-3 text-red-700">
<span></span>
{material.fileSuppressionReason && (
<span> : {material.fileSuppressionReason}</span>)}
</div>)}
{(!material.fileSuppressedAt && material.file && material.contentType) && (
(/image\/.*/.test (material.contentType) && (
<img src={material.file} alt={material.tag.name || undefined}/>))
|| (/video\/.*/.test (material.contentType) && (
@@ -189,13 +221,36 @@ const MaterialDetailPage: FC = () => {
className={inputClass (invalid)}/>)}
</FormField>
<FormField
label="ZIP 出力パス"
messages={fieldErrors.exportPaths}>
{({ describedBy, invalid }) => (
<input
type="text"
value={exportPath}
onChange={e => setExportPath (e.target.value)}
placeholder="伊地知ニジカ/表情/泣き.png"
aria-describedby={describedBy}
aria-invalid={invalid}
className={inputClass (invalid)}/>)}
</FormField>
{/* 送信 */}
<Button
onClick={handleSubmit}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"
disabled={sending}>
</Button>
<div className="flex flex-wrap gap-2">
<Button
onClick={handleSubmit}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"
disabled={sending}>
</Button>
{user?.role === 'admin' && !material.fileSuppressedAt && (
<Button
type="button"
variant="destructive"
onClick={handleSuppress}>
</Button>)}
</div>
</div>
</Tab>
</TabGroup>