sync-docs-and-fixes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
|
||||
import Link from "next/link"
|
||||
import { Calendar, FilePlus, Mail, MessageSquare, Settings } from "lucide-react"
|
||||
import { Calendar, FilePlus, MessageSquare, Settings } from "lucide-react"
|
||||
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
|
||||
@@ -42,7 +42,7 @@ export function ClassScheduleGrid({ schedule, compact = false }: { schedule: Cla
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-5 gap-1 text-center h-full grid-rows-[auto_1fr]">
|
||||
{WEEKDAYS.slice(0, 5).map((day, i) => (
|
||||
{WEEKDAYS.slice(0, 5).map((day) => (
|
||||
<div key={day} className="text-[10px] font-medium text-muted-foreground uppercase py-0.5 border-b bg-muted/20 h-fit">
|
||||
{day}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/shared/components/ui/avat
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
import { formatDate } from "@/shared/lib/utils"
|
||||
|
||||
interface StudentSummary {
|
||||
id: string
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useMemo } from "react"
|
||||
import { useState } from "react"
|
||||
import { Area, AreaChart, CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/shared/components/ui/chart"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/shared/components/ui/tabs"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -31,7 +30,6 @@ interface AssignmentSummary {
|
||||
}
|
||||
|
||||
interface ClassTrendsWidgetProps {
|
||||
classId: string
|
||||
assignments: AssignmentSummary[]
|
||||
compact?: boolean
|
||||
className?: string
|
||||
@@ -121,7 +119,7 @@ export function ClassSubmissionTrendChart({
|
||||
)
|
||||
}
|
||||
|
||||
export function ClassTrendsWidget({ classId, assignments, compact, className }: ClassTrendsWidgetProps) {
|
||||
export function ClassTrendsWidget({ assignments, compact, className }: ClassTrendsWidgetProps) {
|
||||
const [chartTab, setChartTab] = useState<"submission" | "score">("submission")
|
||||
const [selectedSubject, setSelectedSubject] = useState<string>("all")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import {
|
||||
Plus,
|
||||
@@ -10,11 +10,9 @@ import {
|
||||
Users,
|
||||
MapPin,
|
||||
GraduationCap,
|
||||
Search,
|
||||
} from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
@@ -30,30 +28,35 @@ import {
|
||||
} from "@/shared/components/ui/dialog"
|
||||
import { Input } from "@/shared/components/ui/input"
|
||||
import { Label } from "@/shared/components/ui/label"
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/shared/components/ui/tooltip"
|
||||
import type { TeacherClass, ClassScheduleItem } from "../types"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/shared/components/ui/select"
|
||||
import type { TeacherClass } from "../types"
|
||||
import {
|
||||
ensureClassInvitationCodeAction,
|
||||
regenerateClassInvitationCodeAction,
|
||||
joinClassByInvitationCodeAction,
|
||||
} from "../actions"
|
||||
|
||||
const GRADIENTS = [
|
||||
"bg-card border-border",
|
||||
"bg-card border-border",
|
||||
"bg-card border-border",
|
||||
"bg-card border-border",
|
||||
"bg-card border-border",
|
||||
]
|
||||
|
||||
function getClassGradient(id: string) {
|
||||
return "bg-card border-border shadow-sm hover:shadow-md"
|
||||
const getSeededValue = (seed: string, index: number) => {
|
||||
let h = 2166136261
|
||||
const str = `${seed}:${index}`
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
h ^= str.charCodeAt(i)
|
||||
h = Math.imul(h, 16777619)
|
||||
}
|
||||
return (h >>> 0) / 4294967296
|
||||
}
|
||||
|
||||
export function MyClassesGrid({ classes, canCreateClass }: { classes: TeacherClass[]; canCreateClass: boolean }) {
|
||||
export function MyClassesGrid({
|
||||
classes,
|
||||
subjectOptions,
|
||||
}: {
|
||||
classes: TeacherClass[]
|
||||
subjectOptions: string[]
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const [isWorking, setIsWorking] = useState(false)
|
||||
const [joinOpen, setJoinOpen] = useState(false)
|
||||
const [joinSubject, setJoinSubject] = useState("")
|
||||
|
||||
const handleJoin = async (formData: FormData) => {
|
||||
setIsWorking(true)
|
||||
@@ -62,6 +65,7 @@ export function MyClassesGrid({ classes, canCreateClass }: { classes: TeacherCla
|
||||
if (res.success) {
|
||||
toast.success(res.message || "Joined class successfully")
|
||||
setJoinOpen(false)
|
||||
setJoinSubject("")
|
||||
router.refresh()
|
||||
} else {
|
||||
toast.error(res.message || "Failed to join class")
|
||||
@@ -83,6 +87,7 @@ export function MyClassesGrid({ classes, canCreateClass }: { classes: TeacherCla
|
||||
onOpenChange={(open) => {
|
||||
if (isWorking) return
|
||||
setJoinOpen(open)
|
||||
if (!open) setJoinSubject("")
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
@@ -140,12 +145,30 @@ export function MyClassesGrid({ classes, canCreateClass }: { classes: TeacherCla
|
||||
Ask your administrator for the code if you don't have one.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="join-subject" className="text-sm font-medium">
|
||||
教学科目
|
||||
</Label>
|
||||
<Select value={joinSubject} onValueChange={(v) => setJoinSubject(v)}>
|
||||
<SelectTrigger id="join-subject" className="h-12">
|
||||
<SelectValue placeholder={subjectOptions.length === 0 ? "暂无可选科目" : "选择教学科目"} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{subjectOptions.map((subject) => (
|
||||
<SelectItem key={subject} value={subject}>
|
||||
{subject}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<input type="hidden" name="subject" value={joinSubject} />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="p-6 pt-2 bg-muted/5 border-t border-border/50">
|
||||
<Button type="button" variant="ghost" onClick={() => setJoinOpen(false)} disabled={isWorking}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isWorking} className="min-w-[100px]">
|
||||
<Button type="submit" disabled={isWorking || !joinSubject || subjectOptions.length === 0} className="min-w-[100px]">
|
||||
{isWorking ? "Joining..." : "Join Class"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -167,7 +190,7 @@ export function MyClassesGrid({ classes, canCreateClass }: { classes: TeacherCla
|
||||
/>
|
||||
) : (
|
||||
classes.map((c) => (
|
||||
<ClassTicket key={c.id} c={c} onWorkingChange={setIsWorking} isWorking={isWorking} />
|
||||
<ClassTicket key={c.id} c={c} onWorkingChange={setIsWorking} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
@@ -182,11 +205,9 @@ import { ClassTrendsWidget } from "./class-detail/class-trends-widget"
|
||||
|
||||
function ClassTicket({
|
||||
c,
|
||||
isWorking,
|
||||
onWorkingChange,
|
||||
}: {
|
||||
c: TeacherClass
|
||||
isWorking: boolean
|
||||
onWorkingChange: (v: boolean) => void
|
||||
}) {
|
||||
const router = useRouter()
|
||||
@@ -256,7 +277,11 @@ function ClassTicket({
|
||||
{/* Decorative Barcode Strip */}
|
||||
<div className="absolute left-0 top-0 bottom-0 w-1.5 bg-primary/10 flex flex-col justify-between py-2 pointer-events-none">
|
||||
{Array.from({ length: 20 }).map((_, i) => (
|
||||
<div key={i} className="w-full h-px bg-primary/20" style={{ marginBottom: Math.random() * 8 + 2 + 'px' }}></div>
|
||||
<div
|
||||
key={i}
|
||||
className="w-full h-px bg-primary/20"
|
||||
style={{ marginBottom: `${2 + getSeededValue(c.id, i) * 8}px` }}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -320,7 +345,7 @@ function ClassTicket({
|
||||
<div className="absolute right-10 top-1/2 -translate-y-1/2 opacity-[0.03]">
|
||||
<div className="w-8 h-8 bg-current grid grid-cols-4 grid-rows-4 gap-px">
|
||||
{Array.from({ length: 16 }).map((_, i) => (
|
||||
<div key={i} className={cn("bg-transparent", Math.random() > 0.5 && "bg-black")}></div>
|
||||
<div key={i} className={cn("bg-transparent", getSeededValue(`${c.id}-qr`, i) > 0.5 && "bg-black")}></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -373,12 +398,7 @@ function ClassTicket({
|
||||
|
||||
{/* Real Chart */}
|
||||
<div className="h-[140px] w-full">
|
||||
<ClassTrendsWidget
|
||||
classId={c.id}
|
||||
assignments={recentAssignments}
|
||||
compact
|
||||
className="h-full w-full"
|
||||
/>
|
||||
<ClassTrendsWidget assignments={recentAssignments} compact className="h-full w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Clock, MapPin, MoreHorizontal, Pencil, Plus, Trash2 } from "lucide-react"
|
||||
import { MoreHorizontal, Pencil, Plus, Trash2 } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
@@ -518,4 +516,4 @@ export function ScheduleView({
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useQueryState, parseAsString } from "nuqs"
|
||||
import { Search, UserPlus, X, ChevronDown, Check } from "lucide-react"
|
||||
import { Search, UserPlus, ChevronDown, Check } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Input } from "@/shared/components/ui/input"
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select"
|
||||
import { Label } from "@/shared/components/ui/label"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import type { TeacherClass } from "../types"
|
||||
import { enrollStudentByEmailAction } from "../actions"
|
||||
|
||||
@@ -78,8 +77,6 @@ export function StudentsFilters({ classes, defaultClassId }: { classes: TeacherC
|
||||
|
||||
const statusLabel = status === "all" ? "All Status" : (status === "active" ? "Active" : "Inactive")
|
||||
|
||||
const hasFilters = search || classId !== "all" || status !== "all"
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -5,11 +5,10 @@ import { useRouter } from "next/navigation"
|
||||
import { MoreHorizontal, UserCheck, UserX } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/shared/components/ui/avatar"
|
||||
import { Card, CardContent, CardFooter, CardHeader } from "@/shared/components/ui/card"
|
||||
import { cn, formatDate } from "@/shared/lib/utils"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
||||
Reference in New Issue
Block a user