このコミットが含まれているのは:
@@ -31,9 +31,11 @@ type ImportRow = {
|
||||
tagSources?: Record<'automatic' | 'mapped' | 'fixed' | 'manual', string>
|
||||
status: 'ready' | 'warning' | 'error'
|
||||
existing: boolean
|
||||
metadataUrl?: string
|
||||
importStatus?: 'pending' | 'created' | 'skipped' | 'failed' }
|
||||
type ParsedImport = { columns: Column[], rows: { sourceRow: number, values: string[] }[] }
|
||||
type Phase = 'source' | 'mapping' | 'preview' | 'result'
|
||||
type ImportResultStatus = 'created' | 'skipped' | 'failed'
|
||||
|
||||
|
||||
const detectFormat = (source: string): 'csv' | 'tsv' =>
|
||||
@@ -54,7 +56,7 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
const [parsed, setParsed] = useState<ParsedImport | null> (null)
|
||||
const [columns, setColumns] = useState<Column[]> ([])
|
||||
const [rows, setRows] = useState<ImportRow[]> ([])
|
||||
const [results, setResults] = useState<{ sourceRow: number, status: string, post?: { id: number }, errors?: Record<string, string[]> }[]> ([])
|
||||
const [results, setResults] = useState<{ sourceRow: number, status: ImportResultStatus, post?: { id: number }, errors?: Record<string, string[]> }[]> ([])
|
||||
const [mappings, setMappings] = useState<Record<string, { columns: string[] }>> ({ })
|
||||
const [existing, setExisting] = useState<'skip' | 'error'> ('skip')
|
||||
const [loading, setLoading] = useState (false)
|
||||
@@ -181,7 +183,9 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
try
|
||||
{
|
||||
const result = await apiPost<{ created: number, skipped: number, failed: number,
|
||||
rows: { sourceRow: number, status: string, post?: { id: number }, errors?: Record<string, string[]> }[] }> ('/posts/import', { rows: targets, existing })
|
||||
rows: { sourceRow: number, status: ImportResultStatus, post?: { id: number }, errors?: Record<string, string[]> }[] }> ('/posts/import', { rows: targets, existing })
|
||||
if (result.rows.some (row => !(['created', 'skipped', 'failed'] as string[]).includes (row.status)))
|
||||
throw new Error ('不正な登録結果です。')
|
||||
toast ({ title: `登録完了: ${result.created} 件`,
|
||||
description: `スキップ ${result.skipped} 件、失敗 ${result.failed} 件` })
|
||||
setPhase ('result')
|
||||
@@ -295,15 +299,17 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
||||
const RowCard = ({ row, index, update, updateURL }: { row: ImportRow, index: number, update: (index: number, field: string, value: string) => void, updateURL: (index: number, value: string) => void }) => (
|
||||
<article className="space-y-2 rounded border bg-white p-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100">
|
||||
<p>行 {row.sourceRow}・{row.status}</p>
|
||||
{row.importStatus === 'created' ? <p className="rounded bg-green-100 p-2 dark:bg-green-900">登録済み。この行は編集・再登録できません。</p> : <>
|
||||
<EditableValue label="URL" value={row.url} origin={row.provenance.url} onCommit={value => updateURL (index, value)}/>
|
||||
<EditableValue label="タイトル" value={row.attributes.title ?? ''} origin={row.provenance.title} onCommit={value => update (index, 'title', value)}/>
|
||||
<EditableValue label="サムネール基底 URL" value={row.attributes.thumbnailBase ?? ''} origin={row.provenance.thumbnailBase} onCommit={value => update (index, 'thumbnailBase', value)}/>
|
||||
{row.attributes.thumbnailBase && <img src={row.attributes.thumbnailBase} alt="サムネール" className="max-h-32" onError={ev => { ev.currentTarget.hidden = true }}/>}
|
||||
<ThumbnailPreview url={row.attributes.thumbnailBase ?? ''}/>
|
||||
<EditableValue label="作成日時 From" value={row.attributes.originalCreatedFrom ?? ''} origin={row.provenance.originalCreatedFrom} onCommit={value => update (index, 'originalCreatedFrom', value)}/>
|
||||
<EditableValue label="作成日時 Before" value={row.attributes.originalCreatedBefore ?? ''} origin={row.provenance.originalCreatedBefore} onCommit={value => update (index, 'originalCreatedBefore', value)}/>
|
||||
<EditableValue label="動画時間" value={String (row.attributes.duration ?? '')} origin={row.provenance.duration} onCommit={value => update (index, 'duration', value)}/>
|
||||
<EditableValue label="タグ" value={row.attributes.tags ?? ''} origin={row.provenance.tags} onCommit={value => update (index, 'tags', value)}/>
|
||||
<EditableValue label="親投稿" value={row.attributes.parentPostIds ?? ''} origin={row.provenance.parentPostIds} onCommit={value => update (index, 'parentPostIds', value)}/>
|
||||
</>}
|
||||
<FieldError messages={[...row.warnings, ...Object.values (row.errors).flat ()]}/>
|
||||
</article>)
|
||||
|
||||
@@ -324,4 +330,14 @@ const EditableValue = ({ label, value, origin, onCommit }: { label: string, valu
|
||||
</div>
|
||||
}
|
||||
|
||||
const ThumbnailPreview = ({ url }: { url: string }) => {
|
||||
const [failed, setFailed] = useState (false)
|
||||
useEffect (() => setFailed (false), [url])
|
||||
if (!url)
|
||||
return <p className="text-sm">サムネール未指定</p>
|
||||
if (failed)
|
||||
return <p className="text-sm text-red-600 dark:text-red-300">サムネールを表示できません</p>
|
||||
return <img src={url} alt="サムネール" className="max-h-32" onError={() => setFailed (true)}/>
|
||||
}
|
||||
|
||||
export default PostImportPage
|
||||
|
||||
新しい課題から参照
ユーザをブロックする