このコミットが含まれているのは:
2026-07-15 19:57:09 +09:00
コミット 6e5aa1e30f
16個のファイルの変更299行の追加345行の削除
-22
ファイルの表示
@@ -1,22 +0,0 @@
import type { FC, ReactNode } from 'react'
type Props = {
summary: ReactNode
actions: ReactNode }
const PageActionFooter: FC<Props> = ({ summary, actions }) => (
<div
className="shrink-0 border-t bg-white/95 p-4 backdrop-blur
dark:border-neutral-700 dark:bg-neutral-950/95">
<div className="mx-auto flex max-w-6xl flex-col gap-3 md:flex-row
md:items-center md:justify-between">
<div className="flex flex-wrap items-center gap-2 text-sm">
{summary}
</div>
<div className="flex flex-col gap-2 sm:flex-row">
{actions}
</div>
</div>
</div>)
export default PageActionFooter
+25 -68
ファイルの表示
@@ -2,9 +2,7 @@ import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeFi
import FieldError from '@/components/common/FieldError'
import FieldWarning from '@/components/common/FieldWarning'
import FormField from '@/components/common/FormField'
import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge'
import ThumbnailPreview from '@/components/posts/import/ThumbnailPreview'
import { effectivePostImportStatus } from '@/components/posts/import/postImportRowStatus'
import { Button } from '@/components/ui/button'
import { Dialog,
DialogContent,
@@ -17,7 +15,7 @@ import { inputClass } from '@/lib/utils'
import type { FC } from 'react'
import { useEffect, useState } from 'react'
import type { PostImportOrigin,
import type { PostImportResetSnapshot,
PostImportRow } from '@/lib/postImportSession'
type Draft = {
@@ -36,37 +34,11 @@ type Props = {
messageRow?: 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 changedOrigin = (
changed: boolean,
row: PostImportRow,
field: string,
): PostImportOrigin =>
changed ? 'manual' : originOf (row, field)
const originalCreatedOrigin = (
row: PostImportRow,
originalDraft: Draft,
draft: Draft,
): PostImportOrigin => {
const changed =
originalDraft.originalCreatedFrom !== draft.originalCreatedFrom
|| originalDraft.originalCreatedBefore !== draft.originalCreatedBefore
if (changed)
return 'manual'
const manualOrigin =
originOf (row, 'originalCreatedFrom') === 'manual'
|| originOf (row, 'originalCreatedBefore') === 'manual'
return manualOrigin ? 'manual' : 'automatic'
}
onSave: (
payload: { draft: Draft
resetRequested: boolean
resetSnapshot: PostImportResetSnapshot },
) => Promise<boolean> }
const buildDraft = (row: PostImportRow): Draft => ({
url: row.url,
@@ -108,9 +80,6 @@ const PostImportRowDialog: FC<Props> = (
return null
const displayRow = messageRow ?? row
const originalDraft = buildDraft (row)
const fieldOrigin = (field: keyof Draft): PostImportOrigin =>
changedOrigin (draft[field] !== originalDraft[field], row, field)
const update = <Key extends keyof Draft,> (
key: Key,
@@ -121,7 +90,12 @@ const PostImportRowDialog: FC<Props> = (
}
const save = async () => {
if (await onSave (draft))
if (
await onSave ({
draft,
resetRequested: false,
resetSnapshot: row.resetSnapshot })
)
onOpenChange (false)
}
@@ -142,16 +116,12 @@ const PostImportRowDialog: FC<Props> = (
<ThumbnailPreview
url={draft.thumbnailBase}
className="h-28 w-28"/>
<div className="flex flex-wrap gap-2">
<PostImportStatusBadge value={effectivePostImportStatus (displayRow)}/>
</div>
</div>
<div className="space-y-4">
<DialogTextField
label="URL"
value={draft.url}
origin={fieldOrigin ('url')}
warnings={displayRow.fieldWarnings.url}
errors={groupedMessages (displayRow.validationErrors.url,
displayRow.importErrors?.url)}
@@ -159,7 +129,6 @@ const PostImportRowDialog: FC<Props> = (
<DialogTextField
label="タイトル"
value={draft.title}
origin={fieldOrigin ('title')}
warnings={displayRow.fieldWarnings.title}
errors={groupedMessages (displayRow.validationErrors.title,
displayRow.importErrors?.title)}
@@ -167,7 +136,6 @@ const PostImportRowDialog: FC<Props> = (
<DialogTextField
label="サムネール基底 URL"
value={draft.thumbnailBase}
origin={fieldOrigin ('thumbnailBase')}
warnings={displayRow.fieldWarnings.thumbnailBase}
errors={groupedMessages (
displayRow.validationErrors.thumbnailBase,
@@ -175,10 +143,6 @@ const PostImportRowDialog: FC<Props> = (
)}
onChange={value => update ('thumbnailBase', value)}/>
<PostOriginalCreatedTimeField
labelAddon={
<PostImportStatusBadge
value={originalCreatedOrigin (row, originalDraft, draft)}/>
}
originalCreatedFrom={draft.originalCreatedFrom || null}
setOriginalCreatedFrom={value => update ('originalCreatedFrom', value ?? '')}
originalCreatedBefore={draft.originalCreatedBefore || null}
@@ -194,7 +158,6 @@ const PostImportRowDialog: FC<Props> = (
<DialogTextField
label="動画時間"
value={draft.duration}
origin={fieldOrigin ('duration')}
errors={groupedMessages (
displayRow.validationErrors.duration,
displayRow.validationErrors.videoMs,
@@ -205,7 +168,6 @@ const PostImportRowDialog: FC<Props> = (
<DialogAreaField
label="タグ"
value={draft.tags}
origin={fieldOrigin ('tags')}
warnings={displayRow.fieldWarnings.tags}
errors={groupedMessages (displayRow.validationErrors.tags,
displayRow.importErrors?.tags)}
@@ -213,7 +175,6 @@ const PostImportRowDialog: FC<Props> = (
<DialogTextField
label="親投稿"
value={draft.parentPostIds}
origin={fieldOrigin ('parentPostIds')}
errors={groupedMessages (
displayRow.validationErrors.parentPostIds,
displayRow.importErrors?.parentPostIds,
@@ -230,7 +191,15 @@ const PostImportRowDialog: FC<Props> = (
<Button
type="button"
variant="outline"
onClick={() => setDraft (buildResetDraft (row))}>
onClick={async () => {
const resetDraft = buildResetDraft (row)
setDraft (resetDraft)
if (await onSave ({
draft: resetDraft,
resetRequested: true,
resetSnapshot: row.resetSnapshot }))
onOpenChange (false)
}}>
</Button>
<Button
@@ -251,15 +220,14 @@ const PostImportRowDialog: FC<Props> = (
}
const DialogTextField = (
{ label, value, origin, warnings, errors, onChange }: {
{ label, value, warnings, errors, onChange }: {
label: string
value: string
origin: PostImportOrigin
warnings?: string[]
errors?: string[]
onChange: (value: string) => void },
) => (
<FormField label={<DialogLabel label={label} origin={origin}/>} messages={errors}>
<FormField label={label} messages={errors}>
{({ describedBy, invalid }) => (
<>
<input
@@ -273,15 +241,14 @@ const DialogTextField = (
</FormField>)
const DialogAreaField = (
{ label, value, origin, warnings, errors, onChange }: {
{ label, value, warnings, errors, onChange }: {
label: string
value: string
origin: PostImportOrigin
warnings?: string[]
errors?: string[]
onChange: (value: string) => void },
) => (
<FormField label={<DialogLabel label={label} origin={origin}/>} messages={errors}>
<FormField label={label} messages={errors}>
{({ describedBy, invalid }) => (
<>
<textarea
@@ -295,14 +262,4 @@ const DialogAreaField = (
</>)}
</FormField>)
const DialogLabel = (
{ label, origin }: {
label: string
origin: PostImportOrigin },
) => (
<span className="flex items-center gap-2">
<span>{label}</span>
<PostImportStatusBadge value={origin}/>
</span>)
export default PostImportRowDialog
+5 -22
ファイルの表示
@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils'
import PostImportStatusBadge from '@/components/posts/import/PostImportStatusBadge'
import ThumbnailPreview from '@/components/posts/import/ThumbnailPreview'
import { effectivePostImportStatus } from '@/components/posts/import/postImportRowStatus'
import { displayPostImportStatus } from '@/components/posts/import/postImportRowStatus'
import { importPanelToneForRow } from '@/components/posts/import/postImportTone'
import type { FC } from 'react'
@@ -14,14 +14,6 @@ type Props = {
row: PostImportRow
onEdit: () => void }
const summaryError = (row: PostImportRow): string | null => {
const validation = Object.values (row.validationErrors ?? { }).flat ()[0]
if (validation)
return validation
return Object.values (row.importErrors ?? { }).flat ()[0] ?? null
}
const summaryWarning = (row: PostImportRow): string | null =>
Object.values (row.fieldWarnings ?? { }).flat ()[0]
?? row.baseWarnings?.[0]
@@ -34,9 +26,8 @@ const summaryDate = (row: PostImportRow): string =>
const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
const error = summaryError (row)
const warning = error == null ? summaryWarning (row) : null
const displayStatus = effectivePostImportStatus (row)
const warning = summaryWarning (row)
const displayStatus = displayPostImportStatus (row)
return (
<>
@@ -71,13 +62,9 @@ const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
<div className="text-xs text-amber-700 dark:text-amber-200">
{warning}
</div>)}
{error && (
<div className="text-xs text-red-700 dark:text-red-200">
{error}
</div>)}
</div>
<div className="space-y-1">
<PostImportStatusBadge value={displayStatus}/>
{displayStatus != null && <PostImportStatusBadge value={displayStatus}/>}
</div>
<div className="flex justify-end">
<Button
@@ -107,12 +94,8 @@ const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
{row.url}
</div>
<div className="flex flex-wrap gap-2">
<PostImportStatusBadge value={displayStatus}/>
{displayStatus != null && <PostImportStatusBadge value={displayStatus}/>}
</div>
{error && (
<div className="text-xs text-red-700 dark:text-red-200">
{error}
</div>)}
{warning && (
<div className="text-xs text-amber-700 dark:text-amber-200">
{warning}
+2 -12
ファイルの表示
@@ -11,22 +11,12 @@ type Props = {
const LABELS: Record<PostImportBadgeValue, string> = {
ready: '登録可能',
warning: '警告',
error: '要修正',
created: '登録済み',
skipped: 'スキップ',
failed: '失敗',
automatic: '自動取得',
manual: '手修正' }
skipped: 'スキップ' }
const TONES: Record<PostImportBadgeValue, StatusBadgeTone> = {
ready: 'ready',
warning: 'warning',
error: 'danger',
created: 'info',
skipped: 'skipped',
failed: 'danger',
automatic: 'default',
manual: 'manual' }
skipped: 'skipped' }
const PostImportStatusBadge: FC<Props> = ({ value }) => (
+18 -26
ファイルの表示
@@ -1,37 +1,29 @@
import type { PostImportRow } from '@/lib/postImportSession'
export type PostImportEffectiveStatus =
'created'
| 'error'
| 'failed'
| 'ready'
| 'skipped'
| 'warning'
export type PostImportDisplayStatus = 'ready' | 'skipped' | 'warning'
export type PostImportBadgeValue =
PostImportEffectiveStatus
| 'automatic'
| 'manual'
export type PostImportBadgeValue = PostImportDisplayStatus
const hasWarnings = (row: PostImportRow): boolean =>
Object.values (row.fieldWarnings ?? { }).some (_1 => _1.length > 0)
|| row.baseWarnings.length > 0
export const effectivePostImportStatus = (
export const displayPostImportStatus = (
row: PostImportRow,
): PostImportEffectiveStatus => {
if (Object.keys (row.validationErrors ?? { }).length > 0 || row.status === 'error')
return 'error'
): PostImportDisplayStatus | null => {
const skipped =
row.skipReason === 'existing'
|| row.importStatus === 'skipped'
const hidden =
Object.keys (row.validationErrors ?? { }).length > 0
|| row.importStatus === 'failed'
|| row.importStatus === 'created'
switch (row.importStatus)
{
case 'created':
case 'skipped':
case 'failed':
return row.importStatus
default:
return hasWarnings (row) || row.status === 'warning'
? 'warning'
: 'ready'
}
if (skipped)
return 'skipped'
if (hidden)
return null
return hasWarnings (row) || row.status === 'warning'
? 'warning'
: 'ready'
}
+2 -2
ファイルの表示
@@ -1,4 +1,4 @@
import { effectivePostImportStatus } from '@/components/posts/import/postImportRowStatus'
import { displayPostImportStatus } from '@/components/posts/import/postImportRowStatus'
import type { PostImportRow } from '@/lib/postImportSession'
@@ -33,4 +33,4 @@ export const importPanelToneClass = (
}
export const importPanelToneForRow = (row: PostImportRow): string[] =>
importPanelToneClass (effectivePostImportStatus (row))
importPanelToneClass (displayPostImportStatus (row) ?? 'ready')