feat(admin-portal): 完整实现 admin-portal 管理端微前端
包含 src 全部实现、Dockerfile、配置文件等
This commit is contained in:
115
apps/admin-portal/src/app/admin/teachers/page.tsx
Normal file
115
apps/admin-portal/src/app/admin/teachers/page.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { type ReactNode, useState, useEffect } from "react";
|
||||
import { useTeachers, useTeacherFilter } from "@/hooks/use-teachers";
|
||||
import {
|
||||
PageHeader,
|
||||
PaperCard,
|
||||
Input,
|
||||
Select,
|
||||
Badge,
|
||||
LoadingState,
|
||||
ErrorState,
|
||||
EmptyState,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
Pagination,
|
||||
} from "@/components/ui";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
export default function TeachersPage(): ReactNode {
|
||||
const { filter, setPage, setSearch, setStatus } = useTeacherFilter();
|
||||
const { data, loading, error } = useTeachers(filter);
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setSearch(searchInput), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchInput, setSearch]);
|
||||
|
||||
return (
|
||||
<div style={{ padding: 32, maxWidth: 1200, margin: "0 auto" }}>
|
||||
<PageHeader
|
||||
title={t("admin.teachers.title")}
|
||||
description="全校教师一览"
|
||||
/>
|
||||
|
||||
<PaperCard className="p-4 mb-4">
|
||||
<div className="flex gap-3 items-center">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder={t("admin.common.search")}
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
style={{ width: 240 }}
|
||||
/>
|
||||
<Select
|
||||
value={filter.status ?? "all"}
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
>
|
||||
<option value="all">{t("admin.common.all")}</option>
|
||||
<option value="active">{t("admin.common.active")}</option>
|
||||
<option value="disabled">{t("admin.common.disabled")}</option>
|
||||
<option value="locked">{t("admin.common.locked")}</option>
|
||||
</Select>
|
||||
</div>
|
||||
</PaperCard>
|
||||
|
||||
<PaperCard className="p-4">
|
||||
{loading && <LoadingState />}
|
||||
{error && <ErrorState message={error.message} />}
|
||||
{!loading && !error && data && (
|
||||
<>
|
||||
{data.items.length === 0 ? (
|
||||
<EmptyState message={t("admin.common.empty")} />
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
headers={[
|
||||
t("admin.common.email"),
|
||||
t("admin.common.name"),
|
||||
t("admin.common.school"),
|
||||
t("admin.teachers.subjects"),
|
||||
t("admin.teachers.classCount"),
|
||||
t("admin.common.status"),
|
||||
t("admin.common.lastLoginAt"),
|
||||
]}
|
||||
>
|
||||
{data.items.map((teacher) => (
|
||||
<TableRow key={teacher.id}>
|
||||
<TableCell>{teacher.email}</TableCell>
|
||||
<TableCell>{teacher.name}</TableCell>
|
||||
<TableCell>{teacher.schoolName}</TableCell>
|
||||
<TableCell>
|
||||
{teacher.subjects.join(", ") || "—"}
|
||||
</TableCell>
|
||||
<TableCell>{teacher.classCount}</TableCell>
|
||||
<TableCell>
|
||||
<Badge status={teacher.status} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{teacher.lastLoginAt
|
||||
? new Date(teacher.lastLoginAt).toLocaleDateString(
|
||||
"zh-CN",
|
||||
)
|
||||
: "—"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</Table>
|
||||
<Pagination
|
||||
page={data.page}
|
||||
pageSize={data.pageSize}
|
||||
total={data.total}
|
||||
hasNext={data.hasNext}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PaperCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user