Spaces:
Running
Running
Upload 45 files
Browse files- pages/AchievementTeacher.tsx +4 -3
- pages/GameMountain.tsx +12 -9
- pages/GameRewards.tsx +9 -6
pages/AchievementTeacher.tsx
CHANGED
|
@@ -13,7 +13,7 @@ const PRESET_ICONS = [
|
|
| 13 |
{ icon: '⏰', label: '守时' }, { icon: '📝', label: '写作' }, { icon: '🧮', label: '计算' }, { icon: '🔬', label: '科学' }
|
| 14 |
];
|
| 15 |
|
| 16 |
-
export const AchievementTeacher: React.FC = () => {
|
| 17 |
const [loading, setLoading] = useState(true);
|
| 18 |
const [config, setConfig] = useState<AchievementConfig | null>(null);
|
| 19 |
const [students, setStudents] = useState<Student[]>([]);
|
|
@@ -31,11 +31,12 @@ export const AchievementTeacher: React.FC = () => {
|
|
| 31 |
const [selectedAchieveId, setSelectedAchieveId] = useState('');
|
| 32 |
|
| 33 |
const currentUser = api.auth.getCurrentUser();
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
useEffect(() => {
|
| 37 |
loadData();
|
| 38 |
-
}, []);
|
| 39 |
|
| 40 |
const loadData = async () => {
|
| 41 |
if (!homeroomClass) return;
|
|
|
|
| 13 |
{ icon: '⏰', label: '守时' }, { icon: '📝', label: '写作' }, { icon: '🧮', label: '计算' }, { icon: '🔬', label: '科学' }
|
| 14 |
];
|
| 15 |
|
| 16 |
+
export const AchievementTeacher: React.FC<{className?: string}> = ({ className }) => {
|
| 17 |
const [loading, setLoading] = useState(true);
|
| 18 |
const [config, setConfig] = useState<AchievementConfig | null>(null);
|
| 19 |
const [students, setStudents] = useState<Student[]>([]);
|
|
|
|
| 31 |
const [selectedAchieveId, setSelectedAchieveId] = useState('');
|
| 32 |
|
| 33 |
const currentUser = api.auth.getCurrentUser();
|
| 34 |
+
// Use prop or fallback
|
| 35 |
+
const homeroomClass = className || currentUser?.homeroomClass;
|
| 36 |
|
| 37 |
useEffect(() => {
|
| 38 |
loadData();
|
| 39 |
+
}, [className]); // Reload when className changes
|
| 40 |
|
| 41 |
const loadData = async () => {
|
| 42 |
if (!homeroomClass) return;
|
pages/GameMountain.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import React, { useState, useEffect, useRef } from 'react';
|
| 3 |
import { createPortal } from 'react-dom';
|
| 4 |
import { api } from '../services/api';
|
| 5 |
-
import { GameSession, GameTeam, Student, GameRewardConfig, AchievementConfig } from '../types';
|
| 6 |
import { Settings, Plus, Minus, Users, CheckSquare, Loader2, Trash2, X, Flag, Gift, Star, Trophy, Maximize, Minimize, Lock } from 'lucide-react';
|
| 7 |
import { Emoji } from '../components/Emoji';
|
| 8 |
|
|
@@ -200,16 +200,17 @@ export const GameMountain: React.FC<{className?: string}> = ({ className }) => {
|
|
| 200 |
useEffect(() => { loadData(); }, [targetClass]);
|
| 201 |
|
| 202 |
const loadData = async () => {
|
| 203 |
-
|
|
|
|
| 204 |
setLoading(true);
|
| 205 |
|
| 206 |
// Resolve "MY_CLASS" for students
|
| 207 |
let resolvedClass = targetClass;
|
| 208 |
-
if (targetClass === 'MY_CLASS' && currentUser.role === 'STUDENT') {
|
| 209 |
// We need to fetch student's class first? Actually dashboard loads it.
|
| 210 |
// Assuming passed prop is correct or we fetch student
|
| 211 |
const stus = await api.students.getAll();
|
| 212 |
-
const me = stus.find((s: Student) => s.name === (currentUser.trueName || currentUser.username));
|
| 213 |
if(me) resolvedClass = me.className;
|
| 214 |
}
|
| 215 |
|
|
@@ -221,7 +222,8 @@ export const GameMountain: React.FC<{className?: string}> = ({ className }) => {
|
|
| 221 |
|
| 222 |
const filteredStudents = allStudents.filter((s: Student) => s.className === resolvedClass);
|
| 223 |
// Sort
|
| 224 |
-
|
|
|
|
| 225 |
const seatA = parseInt(a.seatNo || '99999');
|
| 226 |
const seatB = parseInt(b.seatNo || '99999');
|
| 227 |
if (seatA !== seatB) return seatA - seatB;
|
|
@@ -254,11 +256,12 @@ export const GameMountain: React.FC<{className?: string}> = ({ className }) => {
|
|
| 254 |
|
| 255 |
// Permissions Check
|
| 256 |
if (isAdmin) setCanEdit(true);
|
| 257 |
-
else if (isTeacher) {
|
| 258 |
// Fetch class info to check homeroomTeacherIds
|
| 259 |
-
const clsList = await api.classes.getAll();
|
| 260 |
-
|
| 261 |
-
|
|
|
|
| 262 |
setCanEdit(true);
|
| 263 |
// Only load Ach config if can edit
|
| 264 |
const ac = await api.achievements.getConfig(resolvedClass);
|
|
|
|
| 2 |
import React, { useState, useEffect, useRef } from 'react';
|
| 3 |
import { createPortal } from 'react-dom';
|
| 4 |
import { api } from '../services/api';
|
| 5 |
+
import { GameSession, GameTeam, Student, GameRewardConfig, AchievementConfig, ClassInfo } from '../types';
|
| 6 |
import { Settings, Plus, Minus, Users, CheckSquare, Loader2, Trash2, X, Flag, Gift, Star, Trophy, Maximize, Minimize, Lock } from 'lucide-react';
|
| 7 |
import { Emoji } from '../components/Emoji';
|
| 8 |
|
|
|
|
| 200 |
useEffect(() => { loadData(); }, [targetClass]);
|
| 201 |
|
| 202 |
const loadData = async () => {
|
| 203 |
+
// FIX: Check for currentUser
|
| 204 |
+
if (!targetClass || targetClass === 'MY_CLASS' && !currentUser?.homeroomClass && currentUser?.role !== 'STUDENT') return;
|
| 205 |
setLoading(true);
|
| 206 |
|
| 207 |
// Resolve "MY_CLASS" for students
|
| 208 |
let resolvedClass = targetClass;
|
| 209 |
+
if (targetClass === 'MY_CLASS' && currentUser?.role === 'STUDENT') {
|
| 210 |
// We need to fetch student's class first? Actually dashboard loads it.
|
| 211 |
// Assuming passed prop is correct or we fetch student
|
| 212 |
const stus = await api.students.getAll();
|
| 213 |
+
const me = stus.find((s: Student) => s.name === (currentUser?.trueName || currentUser?.username));
|
| 214 |
if(me) resolvedClass = me.className;
|
| 215 |
}
|
| 216 |
|
|
|
|
| 222 |
|
| 223 |
const filteredStudents = allStudents.filter((s: Student) => s.className === resolvedClass);
|
| 224 |
// Sort
|
| 225 |
+
// FIX: Add explicit types for sort arguments
|
| 226 |
+
filteredStudents.sort((a: Student, b: Student) => {
|
| 227 |
const seatA = parseInt(a.seatNo || '99999');
|
| 228 |
const seatB = parseInt(b.seatNo || '99999');
|
| 229 |
if (seatA !== seatB) return seatA - seatB;
|
|
|
|
| 256 |
|
| 257 |
// Permissions Check
|
| 258 |
if (isAdmin) setCanEdit(true);
|
| 259 |
+
else if (isTeacher && currentUser) {
|
| 260 |
// Fetch class info to check homeroomTeacherIds
|
| 261 |
+
const clsList = await api.classes.getAll() as ClassInfo[];
|
| 262 |
+
// FIX: Add type for find callback parameter
|
| 263 |
+
const cls = clsList.find((c: ClassInfo) => c.grade + c.className === resolvedClass);
|
| 264 |
+
if (cls && (cls.homeroomTeacherIds?.includes(currentUser._id || '') || cls.teacherName?.includes(currentUser.trueName || currentUser.username))) {
|
| 265 |
setCanEdit(true);
|
| 266 |
// Only load Ach config if can edit
|
| 267 |
const ac = await api.achievements.getConfig(resolvedClass);
|
pages/GameRewards.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { api } from '../services/api';
|
|
| 4 |
import { StudentReward, Student } from '../types';
|
| 5 |
import { Gift, Loader2, Search, Filter, Trash2, Edit, Save, X, ChevronLeft, ChevronRight } from 'lucide-react';
|
| 6 |
|
| 7 |
-
export const GameRewards: React.FC = () => {
|
| 8 |
const [rewards, setRewards] = useState<StudentReward[]>([]);
|
| 9 |
const [total, setTotal] = useState(0);
|
| 10 |
const [page, setPage] = useState(1);
|
|
@@ -32,6 +32,9 @@ export const GameRewards: React.FC = () => {
|
|
| 32 |
const currentUser = api.auth.getCurrentUser();
|
| 33 |
const isStudent = currentUser?.role === 'STUDENT';
|
| 34 |
const isTeacher = currentUser?.role === 'TEACHER';
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
const PAGE_SIZE = 15;
|
| 37 |
|
|
@@ -54,9 +57,9 @@ export const GameRewards: React.FC = () => {
|
|
| 54 |
let targetClass = '';
|
| 55 |
let filteredStudents = allStus;
|
| 56 |
|
| 57 |
-
if (isTeacher &&
|
| 58 |
-
targetClass =
|
| 59 |
-
filteredStudents = allStus.filter((s: Student) => s.className ===
|
| 60 |
}
|
| 61 |
|
| 62 |
const res = await api.rewards.getClassRewards(page, PAGE_SIZE, targetClass);
|
|
@@ -77,7 +80,7 @@ export const GameRewards: React.FC = () => {
|
|
| 77 |
finally { setLoading(false); }
|
| 78 |
};
|
| 79 |
|
| 80 |
-
useEffect(() => { loadData(); }, [page]);
|
| 81 |
|
| 82 |
const handleGrant = async () => {
|
| 83 |
if(!grantForm.studentId) return alert('请选择学生');
|
|
@@ -139,7 +142,7 @@ export const GameRewards: React.FC = () => {
|
|
| 139 |
<div className="flex flex-col h-full bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
| 140 |
<div className="p-4 md:p-6 border-b border-gray-100 flex flex-col md:flex-row justify-between items-start md:items-center gap-4 shrink-0">
|
| 141 |
<h3 className="text-xl font-bold text-gray-800">
|
| 142 |
-
{isStudent ? '我的战利品清单' : '
|
| 143 |
</h3>
|
| 144 |
|
| 145 |
<div className="flex flex-wrap items-center gap-3 w-full md:w-auto">
|
|
|
|
| 4 |
import { StudentReward, Student } from '../types';
|
| 5 |
import { Gift, Loader2, Search, Filter, Trash2, Edit, Save, X, ChevronLeft, ChevronRight } from 'lucide-react';
|
| 6 |
|
| 7 |
+
export const GameRewards: React.FC<{className?: string}> = ({ className }) => {
|
| 8 |
const [rewards, setRewards] = useState<StudentReward[]>([]);
|
| 9 |
const [total, setTotal] = useState(0);
|
| 10 |
const [page, setPage] = useState(1);
|
|
|
|
| 32 |
const currentUser = api.auth.getCurrentUser();
|
| 33 |
const isStudent = currentUser?.role === 'STUDENT';
|
| 34 |
const isTeacher = currentUser?.role === 'TEACHER';
|
| 35 |
+
|
| 36 |
+
// Use prop className or fallback to legacy homeroom
|
| 37 |
+
const homeroomClass = className || currentUser?.homeroomClass;
|
| 38 |
|
| 39 |
const PAGE_SIZE = 15;
|
| 40 |
|
|
|
|
| 57 |
let targetClass = '';
|
| 58 |
let filteredStudents = allStus;
|
| 59 |
|
| 60 |
+
if (isTeacher && homeroomClass) {
|
| 61 |
+
targetClass = homeroomClass;
|
| 62 |
+
filteredStudents = allStus.filter((s: Student) => s.className === homeroomClass);
|
| 63 |
}
|
| 64 |
|
| 65 |
const res = await api.rewards.getClassRewards(page, PAGE_SIZE, targetClass);
|
|
|
|
| 80 |
finally { setLoading(false); }
|
| 81 |
};
|
| 82 |
|
| 83 |
+
useEffect(() => { loadData(); }, [page, className]); // Reload when page or className prop changes
|
| 84 |
|
| 85 |
const handleGrant = async () => {
|
| 86 |
if(!grantForm.studentId) return alert('请选择学生');
|
|
|
|
| 142 |
<div className="flex flex-col h-full bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
| 143 |
<div className="p-4 md:p-6 border-b border-gray-100 flex flex-col md:flex-row justify-between items-start md:items-center gap-4 shrink-0">
|
| 144 |
<h3 className="text-xl font-bold text-gray-800">
|
| 145 |
+
{isStudent ? '我的战利品清单' : `${homeroomClass || '全校'} 奖励核销台`}
|
| 146 |
</h3>
|
| 147 |
|
| 148 |
<div className="flex flex-wrap items-center gap-3 w-full md:w-auto">
|