このコミットが含まれているのは:
2026-07-12 14:03:05 +09:00
コミット aa96ec95d1
10個のファイルの変更1469行の追加639行の削除
+203
ファイルの表示
@@ -0,0 +1,203 @@
import FieldError from '@/components/common/FieldError'
import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge'
import ThumbnailPreview from '@/components/posts/import/ThumbnailPreview'
import { Button } from '@/components/ui/button'
import { Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle } from '@/components/ui/dialog'
import { inputClass } from '@/lib/utils'
import type { FC } from 'react'
import { useEffect, useState } from 'react'
import type { PostImportOrigin,
PostImportRow } from '@/lib/postImportSession'
type Draft = {
url: string
title: string
thumbnailBase: string
originalCreatedFrom: string
originalCreatedBefore: string
duration: string
tags: string
parentPostIds: string }
type Props = {
open: boolean
row: PostImportRow | null
saving: boolean
onOpenChange: (open: boolean) => void
onSave: (draft: Draft) => Promise<boolean> }
const originOf = (
row: PostImportRow,
field: string,
): PostImportOrigin =>
row.provenance[field] ?? 'automatic'
const buildDraft = (row: PostImportRow): Draft => ({
url: row.url,
title: String (row.attributes.title ?? ''),
thumbnailBase: String (row.attributes.thumbnailBase ?? ''),
originalCreatedFrom: String (row.attributes.originalCreatedFrom ?? ''),
originalCreatedBefore: String (row.attributes.originalCreatedBefore ?? ''),
duration: String (row.attributes.duration ?? ''),
tags: String (row.attributes.tags ?? ''),
parentPostIds: String (row.attributes.parentPostIds ?? '') })
const PostImportRowDialog: FC<Props> = (
{ open, row, saving, onOpenChange, onSave },
) => {
const [draft, setDraft] = useState<Draft | null> (null)
useEffect (() => {
if (open && row)
setDraft (buildDraft (row))
}, [open, row])
if (!(row) || !(draft))
return null
const update = <Key extends keyof Draft,> (
key: Key,
value: Draft[Key],
) => {
setDraft (current =>
current ? { ...current, [key]: value } : current)
}
const save = async () => {
if (await onSave (draft))
onOpenChange (false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl px-6 pb-6 pt-7">
<DialogHeader className="pl-8">
<DialogTitle>稿 {row.sourceRow} </DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="grid gap-6 md:grid-cols-[7rem_minmax(0,1fr)]">
<div className="space-y-3">
<ThumbnailPreview
url={draft.thumbnailBase}
className="h-28 w-28"/>
<div className="flex flex-wrap gap-2">
<PostImportStatusBadge value={row.status}/>
<PostImportStatusBadge value={row.importStatus ?? 'pending'}/>
</div>
</div>
<div className="space-y-4">
<DialogField
label="URL"
value={draft.url}
origin={originOf (row, 'url')}
onChange={value => update ('url', value)}/>
<DialogField
label="タイトル"
value={draft.title}
origin={originOf (row, 'title')}
onChange={value => update ('title', value)}/>
<DialogField
label="サムネール基底 URL"
value={draft.thumbnailBase}
origin={originOf (row, 'thumbnailBase')}
onChange={value => update ('thumbnailBase', value)}/>
<div className="grid gap-4 md:grid-cols-2">
<DialogField
label="作成日時 From"
value={draft.originalCreatedFrom}
origin={originOf (row, 'originalCreatedFrom')}
onChange={value => update ('originalCreatedFrom', value)}/>
<DialogField
label="作成日時 Before"
value={draft.originalCreatedBefore}
origin={originOf (row, 'originalCreatedBefore')}
onChange={value => update ('originalCreatedBefore', value)}/>
</div>
<DialogField
label="動画時間"
value={draft.duration}
origin={originOf (row, 'duration')}
onChange={value => update ('duration', value)}/>
<DialogArea
label="タグ"
value={draft.tags}
origin={originOf (row, 'tags')}
onChange={value => update ('tags', value)}/>
<DialogField
label="親投稿"
value={draft.parentPostIds}
origin={originOf (row, 'parentPostIds')}
onChange={value => update ('parentPostIds', value)}/>
<FieldError messages={Object.values (row.validationErrors ?? { }).flat ()}/>
<FieldError messages={Object.values (row.importErrors ?? { }).flat ()}/>
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange (false)}>
</Button>
<Button
type="button"
onClick={save}
disabled={saving}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>)
}
const DialogField = (
{ label, value, origin, onChange }: {
label: string
value: string
origin: PostImportOrigin
onChange: (value: string) => void },
) => (
<label className="block space-y-1">
<span className="flex items-center gap-2 text-sm font-medium">
{label}
<PostImportStatusBadge value={origin}/>
</span>
<input
value={value}
onChange={ev => onChange (ev.target.value)}
className={inputClass (false)}/>
</label>)
const DialogArea = (
{ label, value, origin, onChange }: {
label: string
value: string
origin: PostImportOrigin
onChange: (value: string) => void },
) => (
<label className="block space-y-1">
<span className="flex items-center gap-2 text-sm font-medium">
{label}
<PostImportStatusBadge value={origin}/>
</span>
<textarea
value={value}
rows={4}
onChange={ev => onChange (ev.target.value)}
className={inputClass (false)}/>
</label>)
export default PostImportRowDialog