|
@@ -1,4 +1,4 @@
|
|
|
-import { ArrowRight, BookOpen, CheckCircle2, Clock3, Loader2, Plus, Sparkles, Stars } from 'lucide-react'
|
|
|
|
|
|
|
+import { ArrowRight, Plus } from 'lucide-react'
|
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
|
import { Link } from 'react-router-dom'
|
|
import { Link } from 'react-router-dom'
|
|
|
import { api } from '../api'
|
|
import { api } from '../api'
|
|
@@ -24,9 +24,7 @@ export function Dashboard() {
|
|
|
.finally(() => setLoading(false))
|
|
.finally(() => setLoading(false))
|
|
|
}, [workspaceId])
|
|
}, [workspaceId])
|
|
|
|
|
|
|
|
- const waitingCount = courses.filter((c) => c.status === 'WAITING_PRODUCTION').length
|
|
|
|
|
- const inProductionCount = courses.filter((c) => c.status === 'IN_PRODUCTION').length
|
|
|
|
|
- const completedCount = courses.filter((c) => c.status === 'COMPLETED').length
|
|
|
|
|
|
|
+ const [showSort, setShowSort] = useState(false)
|
|
|
|
|
|
|
|
const sortedCourses = useMemo(() => {
|
|
const sortedCourses = useMemo(() => {
|
|
|
return [...courses].sort((a, b) => {
|
|
return [...courses].sort((a, b) => {
|
|
@@ -43,63 +41,20 @@ export function Dashboard() {
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div className="page dashboard">
|
|
<div className="page dashboard">
|
|
|
- <section className="hero">
|
|
|
|
|
- <div className="orbit orbit-one" />
|
|
|
|
|
- <div className="orbit orbit-two" />
|
|
|
|
|
- <div className="star-dot star-a" />
|
|
|
|
|
- <div className="hero-copy">
|
|
|
|
|
- <p className="eyebrow">
|
|
|
|
|
- <Stars size={15} /> 智能课程工坊
|
|
|
|
|
- </p>
|
|
|
|
|
- <h1>
|
|
|
|
|
- 让每一门好课,
|
|
|
|
|
- <br />
|
|
|
|
|
- <em>留下清晰的星痕。</em>
|
|
|
|
|
- </h1>
|
|
|
|
|
- <p>归集课程素材,建立制作任务。简单几步,让创意从这里启程。</p>
|
|
|
|
|
- <div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
|
|
|
|
- <Link className="primary-button" to="/courses/new" aria-disabled={currentOrganization?.isActive === false} onClick={(e) => { if (currentOrganization?.isActive === false) e.preventDefault() }}>
|
|
|
|
|
- <Plus size={18} /> 创建新课程
|
|
|
|
|
- </Link>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="hero-note">
|
|
|
|
|
- <span>LESS AND COSMOS</span>
|
|
|
|
|
- <p>删去繁复,只留下课程制作真正需要的部分。</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </section>
|
|
|
|
|
-
|
|
|
|
|
<section className="content-section">
|
|
<section className="content-section">
|
|
|
<div className="section-heading" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px' }}>
|
|
<div className="section-heading" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px' }}>
|
|
|
<div>
|
|
<div>
|
|
|
- <h2>{currentOrganization ? `${currentOrganization.name} · 我的课程` : '个人课程'}</h2>
|
|
|
|
|
|
|
+ <h1>{currentOrganization ? `${currentOrganization.name} · 课程` : '个人课程'}</h1>
|
|
|
</div>
|
|
</div>
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', flexWrap: 'wrap' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', flexWrap: 'wrap' }}>
|
|
|
- <div className="metrics">
|
|
|
|
|
- <span>
|
|
|
|
|
- <BookOpen size={16} /> {courses.length} 门课程
|
|
|
|
|
- </span>
|
|
|
|
|
- {waitingCount > 0 && (
|
|
|
|
|
- <span>
|
|
|
|
|
- <Clock3 size={16} /> {waitingCount} 等待制作
|
|
|
|
|
- </span>
|
|
|
|
|
- )}
|
|
|
|
|
- {inProductionCount > 0 && (
|
|
|
|
|
- <span style={{ color: '#0875bd' }}>
|
|
|
|
|
- <Loader2 size={16} className="spin" /> {inProductionCount} 制作中
|
|
|
|
|
- </span>
|
|
|
|
|
- )}
|
|
|
|
|
- {completedCount > 0 && (
|
|
|
|
|
- <span style={{ color: '#2e7d32' }}>
|
|
|
|
|
- <CheckCircle2 size={16} /> {completedCount} 已完成
|
|
|
|
|
- </span>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <span className="dashboard-count">{courses.length} 门课程</span>
|
|
|
|
|
|
|
|
{courses.length > 1 && (
|
|
{courses.length > 1 && (
|
|
|
- <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
|
|
|
- <span style={{ fontSize: '0.85rem', color: '#64748b' }}>排序:</span>
|
|
|
|
|
|
|
+ <div className="dashboard-sort">
|
|
|
|
|
+ <button type="button" className="dashboard-sort-toggle" aria-expanded={showSort} onClick={() => setShowSort(!showSort)}>排序{sortMode !== 'updated_desc' ? ' · 已调整' : ''}</button>
|
|
|
|
|
+ {showSort && <>
|
|
|
<select
|
|
<select
|
|
|
|
|
+ aria-label="课程排序"
|
|
|
style={{ padding: '4px 8px', borderRadius: '6px', border: '1px solid #cbd5e1', fontSize: '0.85rem', background: '#fff' }}
|
|
style={{ padding: '4px 8px', borderRadius: '6px', border: '1px solid #cbd5e1', fontSize: '0.85rem', background: '#fff' }}
|
|
|
value={sortMode}
|
|
value={sortMode}
|
|
|
onChange={(e) => setSortMode(e.target.value as DashboardSortMode)}
|
|
onChange={(e) => setSortMode(e.target.value as DashboardSortMode)}
|
|
@@ -109,8 +64,10 @@ export function Dashboard() {
|
|
|
<option value="created_desc">创建时间(最新优先)</option>
|
|
<option value="created_desc">创建时间(最新优先)</option>
|
|
|
<option value="created_asc">创建时间(最早优先)</option>
|
|
<option value="created_asc">创建时间(最早优先)</option>
|
|
|
</select>
|
|
</select>
|
|
|
|
|
+ </>}
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ <Link className="primary-button" to="/courses/new" aria-disabled={currentOrganization?.isActive === false} onClick={(e) => { if (currentOrganization?.isActive === false) e.preventDefault() }}><Plus size={16} /> 创建课程</Link>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -138,30 +95,15 @@ export function Dashboard() {
|
|
|
<div className="course-grid">
|
|
<div className="course-grid">
|
|
|
{sortedCourses.map((course, index) => (
|
|
{sortedCourses.map((course, index) => (
|
|
|
<Link to={`/courses/${course.id}`} className="course-card" key={course.id}>
|
|
<Link to={`/courses/${course.id}`} className="course-card" key={course.id}>
|
|
|
- <div className={`course-number tone-${index % 3}`}>
|
|
|
|
|
- {String(index + 1).padStart(2, '0')}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div className={`course-number tone-${index % 3}`}>{String(index + 1).padStart(2, '0')}</div>
|
|
|
<div className="course-card-main">
|
|
<div className="course-card-main">
|
|
|
<StatusBadge status={course.status} />
|
|
<StatusBadge status={course.status} />
|
|
|
<h3>{course.name}</h3>
|
|
<h3>{course.name}</h3>
|
|
|
- <p>{course.description || '一门正在准备中的课程。'}</p>
|
|
|
|
|
<div className="course-meta">
|
|
<div className="course-meta">
|
|
|
- <span>
|
|
|
|
|
- {course.assets && course.assets.length > 0
|
|
|
|
|
- ? `${course.assets.length} 份课件资产`
|
|
|
|
|
- : '未上传课件'}
|
|
|
|
|
- </span>
|
|
|
|
|
- {course.status === 'COMPLETED' && (
|
|
|
|
|
- <span style={{ background: '#e8f5e9', color: '#2e7d32', fontWeight: 500 }}>
|
|
|
|
|
- <Sparkles size={12} style={{ display: 'inline', verticalAlign: '-1px' }} /> 成果可查收
|
|
|
|
|
- </span>
|
|
|
|
|
- )}
|
|
|
|
|
- <span title={`创建时间:${formatDateTime(course.createdAt)}`}>
|
|
|
|
|
- 创建于 {formatDate(course.createdAt)}
|
|
|
|
|
- </span>
|
|
|
|
|
<span title={`最后更新时间:${formatDateTime(course.updatedAt || course.createdAt)}`} style={{ color: '#0f172a', fontWeight: 500 }}>
|
|
<span title={`最后更新时间:${formatDateTime(course.updatedAt || course.createdAt)}`} style={{ color: '#0f172a', fontWeight: 500 }}>
|
|
|
- 更新于 {formatDateTime(course.updatedAt || course.createdAt)}
|
|
|
|
|
|
|
+ 更新于 {formatDate(course.updatedAt || course.createdAt)}
|
|
|
</span>
|
|
</span>
|
|
|
|
|
+ <span>{course.status === 'COMPLETED' ? '查看成果' : course.status === 'IN_PRODUCTION' ? '查看进度' : course.status === 'WAITING_PRODUCTION' ? '查看提交状态' : '继续完善'}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<ArrowRight className="card-arrow" size={19} />
|
|
<ArrowRight className="card-arrow" size={19} />
|