このコミットが含まれているのは:
@@ -17,9 +17,11 @@ import { clearPostImportSession,
|
||||
buildNextEditedRow,
|
||||
canEditReviewRow,
|
||||
canRetryResultRow,
|
||||
creatableImportRows,
|
||||
hasExactSourceRows,
|
||||
initialisePreviewRows,
|
||||
isCompletedReviewRow,
|
||||
isExistingSkipRow,
|
||||
isNonRecoverableFailedRow,
|
||||
mergeImportResults,
|
||||
mergeValidatedImportRow,
|
||||
mergeValidatedImportRows,
|
||||
@@ -29,7 +31,8 @@ import { clearPostImportSession,
|
||||
resultRowMessages,
|
||||
reviewSummaryCounts,
|
||||
retryImportRow,
|
||||
savePostImportSession } from '@/lib/postImportSession'
|
||||
savePostImportSession,
|
||||
validatableImportRows } from '@/lib/postImportSession'
|
||||
import { canEditContent } from '@/lib/users'
|
||||
import Forbidden from '@/pages/Forbidden'
|
||||
|
||||
@@ -59,6 +62,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const [loading, setLoading] = useState (false)
|
||||
const [loadingRow, setLoadingRow] = useState<number | null> (null)
|
||||
const [editingRow, setEditingRow] = useState<PostImportRow | null> (null)
|
||||
const [showExistingRows, setShowExistingRows] = useState (false)
|
||||
const sessionRef = useRef<PostImportSession | null> (null)
|
||||
|
||||
useEffect (() => {
|
||||
@@ -78,13 +82,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const rows = session?.rows ?? []
|
||||
const counts = useMemo (() => reviewSummaryCounts (rows), [rows])
|
||||
const processable = useMemo (
|
||||
() => processableImportRows (rows),
|
||||
[rows])
|
||||
const creatable = useMemo (
|
||||
() => creatableImportRows (rows),
|
||||
[rows])
|
||||
const reviewRows =
|
||||
const sortedRows =
|
||||
session?.repairMode === 'failed'
|
||||
? (
|
||||
[...rows].sort ((a, b) => {
|
||||
@@ -93,6 +91,8 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
return aRepair - bRepair || a.sourceRow - b.sourceRow
|
||||
}))
|
||||
: rows
|
||||
const existingRows = sortedRows.filter (row => isExistingSkipRow (row))
|
||||
const reviewRows = sortedRows.filter (row => !(isExistingSkipRow (row)))
|
||||
const busy = loading || loadingRow != null
|
||||
|
||||
const persistSession = (nextSession: PostImportSession) => {
|
||||
@@ -110,6 +110,35 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
navigate ('/posts')
|
||||
}
|
||||
|
||||
const toggleManualSkip = (sourceRow: number, checked: boolean) => {
|
||||
if (busy)
|
||||
return
|
||||
|
||||
const currentSession = sessionRef.current
|
||||
if (currentSession == null)
|
||||
return
|
||||
|
||||
const nextRows = currentSession.rows.map (row => {
|
||||
if (row.sourceRow !== sourceRow)
|
||||
return row
|
||||
if (isExistingSkipRow (row)
|
||||
|| row.importStatus === 'created'
|
||||
|| isNonRecoverableFailedRow (row))
|
||||
return row
|
||||
return {
|
||||
...row,
|
||||
skipReason: checked ? 'manual' : undefined,
|
||||
existingPostId: checked ? undefined : row.existingPostId }
|
||||
})
|
||||
const nextSession = {
|
||||
...currentSession,
|
||||
rows: nextRows,
|
||||
repairMode: resultRepairMode (nextRows) }
|
||||
persistSession (nextSession)
|
||||
if (nextRows.every (row => isCompletedReviewRow (row)))
|
||||
finishImport ()
|
||||
}
|
||||
|
||||
useEffect (() => {
|
||||
if (editingRow == null || session?.repairMode !== 'failed')
|
||||
return
|
||||
@@ -154,8 +183,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
{
|
||||
const validated = await apiPost<{ rows: PostImportRow[] }> ('/posts/import/validate', {
|
||||
rows:
|
||||
nextRows
|
||||
.filter (currentRow => currentRow.importStatus !== 'created')
|
||||
validatableImportRows (nextRows)
|
||||
.map (currentRow => ({
|
||||
sourceRow: currentRow.sourceRow,
|
||||
url: currentRow.url,
|
||||
@@ -256,7 +284,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const pendingRows = retryImportRow (initialSession.rows, sourceRow)
|
||||
const pendingSession = { ...initialSession, rows: pendingRows }
|
||||
persistSession (pendingSession)
|
||||
const requestedRows = pendingRows.filter (row => row.importStatus !== 'created')
|
||||
const requestedRows = validatableImportRows (pendingRows)
|
||||
const validated = await apiPost<{ rows: PostImportRow[] }> ('/posts/import/validate', {
|
||||
rows: requestedRows.map (row => ({
|
||||
sourceRow: row.sourceRow,
|
||||
@@ -362,8 +390,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
rows: nextRows,
|
||||
repairMode: resultRepairMode (nextRows) }
|
||||
persistSession (resultSession)
|
||||
if (nextRows.every (row =>
|
||||
row.importStatus === 'created' || row.importStatus === 'skipped'))
|
||||
if (nextRows.every (row => isCompletedReviewRow (row)))
|
||||
{
|
||||
finishImport ()
|
||||
return
|
||||
@@ -387,14 +414,23 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const submit = async () => {
|
||||
const currentSession = sessionRef.current
|
||||
if (currentSession == null || processable.length === 0 || busy)
|
||||
if (currentSession == null || busy)
|
||||
return
|
||||
if (rows.every (row => isCompletedReviewRow (row)))
|
||||
{
|
||||
finishImport ()
|
||||
return
|
||||
}
|
||||
|
||||
setLoading (true)
|
||||
try
|
||||
{
|
||||
const validatableRows =
|
||||
currentSession.rows.filter (row => row.importStatus !== 'created')
|
||||
const validatableRows = validatableImportRows (currentSession.rows)
|
||||
if (validatableRows.length === 0)
|
||||
{
|
||||
finishImport ()
|
||||
return
|
||||
}
|
||||
const validated = await apiPost<{ rows: PostImportRow[] }> ('/posts/import/validate', {
|
||||
rows:
|
||||
validatableRows.map (row => ({
|
||||
@@ -470,8 +506,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
rows: nextRows,
|
||||
repairMode: resultRepairMode (nextRows) }
|
||||
persistSession (nextSession)
|
||||
if (nextRows.every (row =>
|
||||
row.importStatus === 'created' || row.importStatus === 'skipped'))
|
||||
if (nextRows.every (row => isCompletedReviewRow (row)))
|
||||
{
|
||||
finishImport ()
|
||||
}
|
||||
@@ -492,24 +527,52 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
if (session == null)
|
||||
return null
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{`広場に投稿を追加 | ${ SITE_TITLE }`}</title>
|
||||
<title>{`追加内容確認 | ${ SITE_TITLE }`}</title>
|
||||
</Helmet>
|
||||
|
||||
<MainArea className="min-h-0">
|
||||
<div className="mx-auto max-w-6xl space-y-4 p-4">
|
||||
<PageTitle>広場に投稿を追加</PageTitle>
|
||||
<PageTitle>追加内容確認</PageTitle>
|
||||
|
||||
{counts.existingSkipped > 0 && (
|
||||
<div className="rounded-lg border p-4">
|
||||
<button
|
||||
type="button"
|
||||
className="text-left text-sm font-medium"
|
||||
onClick={() => setShowExistingRows (current => !(current))}>
|
||||
既存投稿による自動スキップ {counts.existingSkipped}件
|
||||
</button>
|
||||
{showExistingRows && (
|
||||
<div className="mt-3 space-y-3">
|
||||
{existingRows.map (row => (
|
||||
<div key={row.sourceRow} id={`post-import-row-${ row.sourceRow }`}>
|
||||
<PostImportRowSummary
|
||||
row={row}
|
||||
showActions={false}
|
||||
showSkipToggle={false}
|
||||
rowMessages={resultRowMessages (row)}/>
|
||||
</div>))}
|
||||
</div>)}
|
||||
</div>)}
|
||||
|
||||
<div className="space-y-3">
|
||||
{reviewRows.map (row => (
|
||||
<div key={row.sourceRow} id={`post-import-row-${ row.sourceRow }`}>
|
||||
<PostImportRowSummary
|
||||
row={row}
|
||||
showSkipToggle={!(isExistingSkipRow (row))}
|
||||
editDisabled={busy}
|
||||
retryDisabled={busy}
|
||||
skipDisabled={
|
||||
busy
|
||||
|| isExistingSkipRow (row)
|
||||
|| row.importStatus === 'created'
|
||||
|| isNonRecoverableFailedRow (row)}
|
||||
onEdit={() => void editRow (row)}
|
||||
onToggleSkip={checked => toggleManualSkip (row.sourceRow, checked)}
|
||||
onRetry={canRetryResultRow (row) ? () => void retry (row.sourceRow) : undefined}
|
||||
rowMessages={resultRowMessages (row)}/>
|
||||
</div>))}
|
||||
@@ -519,9 +582,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
<PostImportFooter
|
||||
loading={busy}
|
||||
processableCount={processable.length}
|
||||
creatableCount={creatable.length}
|
||||
skipPlannedCount={counts.skipPlanned}
|
||||
creatableCount={counts.creatable}
|
||||
manualSkippedCount={counts.manualSkipped}
|
||||
existingSkippedCount={counts.existingSkipped}
|
||||
pendingOrErrorCount={counts.pendingOrError}
|
||||
onBack={() => navigate ('/posts/new')}
|
||||
onSubmit={submit}/>
|
||||
</>)
|
||||
@@ -529,16 +593,18 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const PostImportFooter = (
|
||||
{ loading,
|
||||
processableCount,
|
||||
creatableCount,
|
||||
skipPlannedCount,
|
||||
manualSkippedCount,
|
||||
existingSkippedCount,
|
||||
pendingOrErrorCount,
|
||||
onBack,
|
||||
onSubmit }: { loading: boolean
|
||||
processableCount: number
|
||||
creatableCount: number
|
||||
skipPlannedCount: number
|
||||
onBack: () => void
|
||||
onSubmit: () => void },
|
||||
onSubmit }: { loading: boolean
|
||||
creatableCount: number
|
||||
manualSkippedCount: number
|
||||
existingSkippedCount: number
|
||||
pendingOrErrorCount: number
|
||||
onBack: () => void
|
||||
onSubmit: () => void },
|
||||
) => (
|
||||
<div
|
||||
className="shrink-0 border-t bg-white/95 p-4 backdrop-blur
|
||||
@@ -546,9 +612,10 @@ const PostImportFooter = (
|
||||
<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">
|
||||
<span>処理対象 {processableCount}件</span>
|
||||
<span>登録対象 {creatableCount}件</span>
|
||||
<span>スキップ予定 {skipPlannedCount}件</span>
|
||||
<span>作成対象 {creatableCount}件</span>
|
||||
<span>手動スキップ {manualSkippedCount}件</span>
|
||||
<span>既存投稿による自動スキップ {existingSkippedCount}件</span>
|
||||
<span>error/未処理 {pendingOrErrorCount}件</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Button type="button" variant="outline" onClick={onBack} disabled={loading}>
|
||||
@@ -557,7 +624,7 @@ const PostImportFooter = (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
disabled={loading || processableCount === 0}>
|
||||
disabled={loading || creatableCount === 0}>
|
||||
取込実行
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
新しい課題から参照
ユーザをブロックする