Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from "react"; | |
| interface Student { | |
| index: number; | |
| name: string; | |
| id: string; | |
| } | |
| interface StudentSelectorProps { | |
| students: Student[]; | |
| onSelectionChange: (selectedIndices: number[]) => void; | |
| } | |
| export function StudentSelector({ | |
| students, | |
| onSelectionChange, | |
| }: StudentSelectorProps) { | |
| const [selected, setSelected] = useState<Set<number>>( | |
| () => new Set(students.map((s) => s.index)) | |
| ); | |
| useEffect(() => { | |
| onSelectionChange(Array.from(selected).sort((a, b) => a - b)); | |
| }, [selected]); | |
| const toggleAll = () => { | |
| if (selected.size === students.length) { | |
| setSelected(new Set()); | |
| } else { | |
| setSelected(new Set(students.map((s) => s.index))); | |
| } | |
| }; | |
| const toggle = (index: number) => { | |
| setSelected((prev) => { | |
| const next = new Set(prev); | |
| if (next.has(index)) { | |
| next.delete(index); | |
| } else { | |
| next.add(index); | |
| } | |
| return next; | |
| }); | |
| }; | |
| const allSelected = selected.size === students.length; | |
| return ( | |
| <div className="space-y-3"> | |
| <div className="flex items-center gap-3"> | |
| <h3 className="font-display text-sm font-semibold text-[var(--color-text)]"> | |
| 選擇學生 | |
| </h3> | |
| <button | |
| onClick={toggleAll} | |
| className="btn btn-outline text-xs !py-1 !px-3" | |
| > | |
| {allSelected ? "取消全選" : "全選"} | |
| </button> | |
| <span className="text-sm text-[var(--color-text-muted)]"> | |
| 已選 {selected.size} / {students.length} 位學生 | |
| </span> | |
| </div> | |
| <div className="card p-4 max-h-64 overflow-y-auto"> | |
| <div className="grid gap-1.5 sm:grid-cols-2"> | |
| {students.map((s) => ( | |
| <label | |
| key={s.index} | |
| className={`flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer transition-colors ${ | |
| selected.has(s.index) | |
| ? "bg-[var(--color-primary)]/10" | |
| : "hover:bg-[var(--color-surface)]" | |
| }`} | |
| > | |
| <input | |
| type="checkbox" | |
| checked={selected.has(s.index)} | |
| onChange={() => toggle(s.index)} | |
| className="w-4 h-4 rounded accent-[var(--color-primary)]" | |
| /> | |
| <span className="text-sm font-medium text-[var(--color-text)]"> | |
| {s.name} | |
| </span> | |
| {s.id && ( | |
| <span className="text-xs text-[var(--color-text-muted)]"> | |
| ({s.id}) | |
| </span> | |
| )} | |
| </label> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |