/* 匯入 & AI 學習分析畫面 — screens2.jsx */ const { useState: useState2, useMemo: useMemo2 } = React; const { Icon: Icon2, Card: Card2, StatCard: StatCard2, TypeBadge: TypeBadge2, ScoreChip: ScoreChip2, RiskLight: RiskLight2, Button: Button2, SectionTitle: SectionTitle2, TrendLine: TrendLine2 } = window; const D2 = window.APPDATA; const fmt2 = window.fmtDate; /* ============================================================ 匯入成績(老師)— 三步驟流程 ============================================================ */ function ImportScreen({ commitImport }) { const [step, setStep] = useState2(1); const [source, setSource] = useState2(null); // "excel" | "sheet" const [done, setDone] = useState2(false); const rows = D2.importSampleRows; const okRows = rows.filter(r => r.status !== "error"); const errRows = rows.filter(r => r.status === "error"); const steps = ["上傳來源", "預覽與對應", "確認匯入"]; function reset() { setStep(1); setSource(null); setDone(false); } return (
匯入成績
支援 Excel 檔上傳,或連結 Google 試算表批次匯入。
{/* 步驟列 */}
{steps.map((s, i) => { const n = i + 1; const state = done ? "done" : n < step ? "done" : n === step ? "active" : "idle"; return (
{state === "done" ? : n} {s}
{i < steps.length - 1 &&
} ); })}
{done ? (

匯入完成

已成功匯入 {okRows.length} 筆成績{errRows.length ? <>,略過 {errRows.length} 筆有問題的資料。 : "。"}

再匯入一批 完成
) : ( <> {/* Step 1 — 上傳來源 */} {step === 1 && ( 選擇匯入來源
{source === "excel" && (
將 Excel 檔拖曳到這裡,或點擊選擇
已選擇範例檔:七年三班_第5次小考.xlsx(8 列)
)} {source === "sheet" && (
連結有效
)}
setStep(2)}>下一步:預覽
)} {/* Step 2 — 預覽與對應 */} {step === 2 && ( {okRows.length} 筆可匯入 {errRows.length > 0 && {errRows.length} 筆需檢查} }>預覽資料・欄位對應
{[["座號", "seat"], ["姓名", "name"], ["評量類型", "type"], ["評量項目", "title"], ["日期", "date"], ["得分", "score"]].map(([cn]) => ( {cn} ))}
座號
姓名
類型
評量項目
日期
得分
{rows.map((r, i) => (
{String(r.seat).padStart(2, "0")}
{r.name}
{r.title}
{fmt2(r.date)}
{r.score == null ? 缺漏 : }
))}
{errRows.length > 0 && (
有 {errRows.length} 列得分缺漏,匯入時將自動略過。請確認後再繼續。
)}
setStep(1)}>上一步 setStep(3)}>下一步:確認
)} {/* Step 3 — 確認 */} {step === 3 && ( 確認匯入
來源 {source === "excel" ? "Excel 檔・七年三班_第5次小考.xlsx" : "Google 試算表"}
評量項目 第 5 次小考・統計圖表
可匯入筆數 {okRows.length} 筆
略過筆數 {errRows.length} 筆(缺漏)
匯入後,這些成績會立即出現在「全班成績」與學生的「我的成績」中。
setStep(2)}>上一步 { commitImport && commitImport(okRows); setDone(true); }}> 確認匯入 {okRows.length} 筆
)} )}
); } /* ============================================================ AI 學習分析(學生 / 小老師) ============================================================ */ function AnalysisScreen({ studentId, grades }) { const student = D2.students.find(s => s.id === studentId); const myGrades = useMemo2( () => grades.filter(g => g.studentId === studentId).slice().sort((a, b) => a.date < b.date ? -1 : 1), [grades, studentId] ); const risk = useMemo2(() => D2.riskFor(studentId, grades), [grades, studentId]); const trend = myGrades.map(g => g.score); // 各類型平均 const byType = useMemo2(() => { return D2.assessmentTypes.map(t => { const sc = myGrades.filter(g => g.type === t).map(g => g.score); return { type: t, avg: sc.length ? Math.round(sc.reduce((a, b) => a + b, 0) / sc.length) : null }; }).filter(x => x.avg != null); }, [myGrades]); const weakest = byType.slice().sort((a, b) => a.avg - b.avg)[0]; if (!risk) return
尚無足夠成績可分析。
; return (
學習分析
{student.name}・AI 依你的平時成績預估期末表現
{/* 大型風險面板 */}
AI 期末預測
{risk.headline}

{risk.advice}

整體平均 {risk.overall} 近期平均 {risk.recentAvg} = 0 ? "up" : "down"}> = 0 ? "arrowUp" : "arrowDown"} size={13} /> 趨勢 {risk.trend >= 0 ? "+" : ""}{risk.trend}
{risk.predicted}
預估期末平均
及格門檻 60 分
學期成績趨勢
第一次評量 虛線 = 及格線 60 最近一次
各類型表現
{byType.map(b => (
))}
{weakest && (
建議優先加強 {weakest.type}(目前平均 {weakest.avg} 分),提升空間最大。
)}
AI 預測僅供參考,實際成績以老師評定為準。
); } window.ImportScreen = ImportScreen; window.AnalysisScreen = AnalysisScreen;