import { Clock3, Download, FileText, RefreshCw, Search, UserRound } from 'lucide-react' import { useEffect, useState } from 'react' import { api, downloadPrivateFile } from '../../api' import { ProtectedImage } from '../../components/ProtectedImage' import { formatDateTime } from '../../utils/date' import { notesHtml, notesText } from '../../utils/lectureNotes' import type { Course } from '../../types' export function AdminProductionQueue() { const [courses, setCourses] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [keyword, setKeyword] = useState('') const [query, setQuery] = useState('') const [status, setStatus] = useState<'WAITING_PRODUCTION' | 'IN_PRODUCTION'>('WAITING_PRODUCTION') const [busyId, setBusyId] = useState('') const [notice, setNotice] = useState('') const [files, setFiles] = useState>({}) const [notes, setNotes] = useState>({}) const [points, setPoints] = useState>({}) useEffect(() => { let active = true setLoading(true) setError('') api.admin.listCourses({ status, keyword: query }) .then(({ courses }) => { if (active) setCourses(courses) }) .catch((err) => { if (active) setError(err.message) }) .finally(() => { if (active) setLoading(false) }) return () => { active = false } }, [query, status]) const refresh = () => { setLoading(true) setError('') api.admin.listCourses({ status, keyword: query }) .then(({ courses }) => setCourses(courses)) .catch((err) => setError(err.message)) .finally(() => setLoading(false)) } const run = async (id: string, action: () => Promise, message: string) => { setBusyId(id) setError('') setNotice('') try { await action(); setNotice(message); refresh() } catch (err) { setError((err as Error).message) } finally { setBusyId('') } } return

待制作课程

团队制作准备清单 · 查看已提交课程的资料、素材和数字讲师

{loading ? '…' : courses.length} 门
{ event.preventDefault(); setQuery(keyword.trim()) }}> setKeyword(event.target.value)} placeholder="搜索课程、分类或提交人" aria-label="搜索待制作课程" />
{error &&
{error}
} {notice &&
{notice}
} {loading ?
正在读取待制作课程…
: !error && courses.length === 0 ?

{query ? '没有匹配的课程' : status === 'WAITING_PRODUCTION' ? '当前没有等待制作的课程' : '当前没有制作中的课程'}

{query ? '可以调整搜索词或清空搜索。' : '课程状态变化后,这里的列表会随之更新。'}

:
{courses.map((course) => { const instructor = course.digitalInstructorSnapshot const notesCount = course.episodes.filter((episode) => notesText(episode.lectureNotes || '').trim()).length return
等待制作

{course.name}

{course.description || '未填写课程简介'}

{course.creator?.contactName || '未知提交人'} · {course.creator?.phone || '无联系电话'} · {course.organizationName || '个人工作区'} 提交于 {formatDateTime(course.submittedAt || course.createdAt)} {course.episodes.length} 集 · {notesCount} 份讲稿 · {course.assets.length} 份课程素材

课程资料与分集

{course.episodes.length ?
    {course.episodes.map((episode) =>
  • 第 {episode.episodeNumber} 集 · {episode.title}{episode.summary || '未填写分集简介'} · {notesText(episode.lectureNotes || '').trim() ? `${notesText(episode.lectureNotes).length} 字讲稿` : '暂无讲稿'}
  • )}
:

暂无分集资料

}

上传素材({course.assets.length})

{course.assets.length ?
    {course.assets.map((asset) =>
  • {asset.originalName}
  • )}
:

暂无上传素材

}

数字讲师

{instructor ?
{instructor.images?.map((photo) => )}
{instructor.name}

{instructor.note || '无备注'}

照片 {instructor.images?.length || 0} 张 · 录音 {instructor.audio?.length || 0} 份{instructor.audio?.map((audio) => )}
:

未保存数字讲师快照,请进入详情核对历史讲师资料。

}
查看完整制作资料

提交人资料

姓名:{course.creator?.contactName || '未填写'}

电话:{course.creator?.phone || '未填写'}

微信:{course.creator?.wechat || '未填写'}

分集讲稿

{course.episodes.map((episode) =>
第 {episode.episodeNumber} 集 · {episode.title}{episode.lectureNotes ?
:

未填写讲稿

}
)}
{status === 'WAITING_PRODUCTION' ? : <> {!!course.deliverables?.length &&
{course.deliverables.map((file) => )}
}