feat: adopt kumo green ui system
This commit is contained in:
@@ -3,6 +3,7 @@ import { BedDouble, DoorOpen, History, Wrench } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { getCurrentAuthContext } from "@/modules/core/server/auth";
|
||||
import { hasPermission } from "@/modules/core/server/permissions";
|
||||
import { readData } from "@/modules/core/server/store";
|
||||
@@ -90,39 +91,39 @@ export default async function BedsPage(): Promise<React.ReactElement> {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[620px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">房间</th>
|
||||
<th className="px-4 py-3 font-medium">编号</th>
|
||||
<th className="px-4 py-3 font-medium">容量</th>
|
||||
<th className="px-4 py-3 font-medium">床位</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[620px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">房间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">编号</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">容量</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">床位</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{rooms.map((room) => (
|
||||
<tr key={room.id}>
|
||||
<td className="px-4 py-3 font-medium">{room.name}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{room.code}</td>
|
||||
<td className="px-4 py-3">{room.capacity}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={room.id}>
|
||||
<Table.Cell className="px-4 py-3 font-medium">{room.name}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{room.code}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{room.capacity}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
{room.occupiedBedCount}/{room.bedCount}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={room.status === "active" ? "success" : "danger"}>{room.status}</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{rooms.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
暂无房间,请通过 /api/facilities/rooms 创建
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -133,22 +134,22 @@ export default async function BedsPage(): Promise<React.ReactElement> {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[700px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">房间</th>
|
||||
<th className="px-4 py-3 font-medium">床位</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
<th className="px-4 py-3 font-medium">当前老人</th>
|
||||
<th className="px-4 py-3 font-medium">备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[700px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">房间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">床位</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">当前老人</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">备注</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{beds.map((bed) => (
|
||||
<tr key={bed.id}>
|
||||
<td className="px-4 py-3">{bed.roomName}</td>
|
||||
<td className="px-4 py-3 font-medium">{bed.code}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={bed.id}>
|
||||
<Table.Cell className="px-4 py-3">{bed.roomName}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 font-medium">{bed.code}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge
|
||||
variant={
|
||||
bed.status === "available"
|
||||
@@ -162,20 +163,20 @@ export default async function BedsPage(): Promise<React.ReactElement> {
|
||||
>
|
||||
{bed.status}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3">{bed.currentElderName ?? "-"}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{bed.notes || "-"}</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{bed.currentElderName ?? "-"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{bed.notes || "-"}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{beds.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
暂无床位,请通过 /api/facilities/beds 创建
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -187,45 +188,45 @@ export default async function BedsPage(): Promise<React.ReactElement> {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[820px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">老人</th>
|
||||
<th className="px-4 py-3 font-medium">床位</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
<th className="px-4 py-3 font-medium">入住时间</th>
|
||||
<th className="px-4 py-3 font-medium">结束时间</th>
|
||||
<th className="px-4 py-3 font-medium">备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[820px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">老人</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">床位</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">入住时间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">结束时间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">备注</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{admissions.map((admission) => (
|
||||
<tr key={admission.id}>
|
||||
<td className="px-4 py-3 font-medium">{admission.elderName}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={admission.id}>
|
||||
<Table.Cell className="px-4 py-3 font-medium">{admission.elderName}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
{admission.roomName}-{admission.bedCode}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={admission.status === "active" ? "success" : "secondary"}>{admission.status}</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">
|
||||
{new Date(admission.admittedAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">
|
||||
{admission.dischargedAt ? new Date(admission.dischargedAt).toLocaleString("zh-CN") : "-"}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{admission.notes || "-"}</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{admission.notes || "-"}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{admissions.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
暂无入住历史
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { getCurrentAuthContext } from "@/modules/core/server/auth";
|
||||
import { hasPermission } from "@/modules/core/server/permissions";
|
||||
import { readData } from "@/modules/core/server/store";
|
||||
@@ -58,40 +59,40 @@ export default async function AuditPage({ searchParams }: AuditPageProps): Promi
|
||||
total={filteredLogs.length}
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[900px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">时间</th>
|
||||
<th className="px-4 py-3 font-medium">操作者</th>
|
||||
<th className="px-4 py-3 font-medium">动作</th>
|
||||
<th className="px-4 py-3 font-medium">对象</th>
|
||||
<th className="px-4 py-3 font-medium">结果</th>
|
||||
<th className="px-4 py-3 font-medium">原因</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[900px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">时间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">操作者</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">动作</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">对象</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">结果</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">原因</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{visibleLogs.map((log) => (
|
||||
<tr className="table-row-enter hover:bg-secondary/45" key={log.id}>
|
||||
<td className="px-4 py-3 text-muted-foreground">{new Date(log.timestamp).toLocaleString("zh-CN")}</td>
|
||||
<td className="px-4 py-3">{log.actorEmail ?? "系统"}</td>
|
||||
<td className="px-4 py-3">{log.action}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
<Table.Row className="table-row-enter hover:bg-secondary/45" key={log.id}>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{new Date(log.timestamp).toLocaleString("zh-CN")}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{log.actorEmail ?? "系统"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{log.action}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">
|
||||
{log.targetType}
|
||||
{log.targetId ? ` / ${log.targetId.slice(0, 8)}` : ""}
|
||||
</td>
|
||||
<td className="px-4 py-3">{log.result}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{log.reason}</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{log.result}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{log.reason}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{visibleLogs.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
暂无匹配审计
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { ArrowLeft, Building2, Users } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LinkButton } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { getCurrentAuthContext, toPublicAccount } from "@/modules/core/server/auth";
|
||||
import { getRoleDefinitions, hasPermission } from "@/modules/core/server/permissions";
|
||||
import { readData } from "@/modules/core/server/store";
|
||||
@@ -64,12 +64,10 @@ export default async function OrganizationDetailPage({
|
||||
<div className="flex flex-col gap-5">
|
||||
<section className="flex flex-col gap-4 border-b pb-5 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<Button asChild size="sm" variant="outline">
|
||||
<Link href="/app/settings/organizations">
|
||||
<ArrowLeft className="size-4" aria-hidden="true" />
|
||||
返回机构列表
|
||||
</Link>
|
||||
</Button>
|
||||
<LinkButton href="/app/settings/organizations" size="sm" variant="outline">
|
||||
<ArrowLeft className="size-4" aria-hidden="true" />
|
||||
返回机构列表
|
||||
</LinkButton>
|
||||
<div className="mt-5 flex items-center gap-3">
|
||||
<span className="inline-flex size-11 items-center justify-center rounded-md bg-secondary text-primary">
|
||||
<Building2 className="size-5" aria-hidden="true" />
|
||||
@@ -131,40 +129,40 @@ export default async function OrganizationDetailPage({
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[760px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">账号</th>
|
||||
<th className="px-4 py-3 font-medium">角色</th>
|
||||
<th className="px-4 py-3 font-medium">成员状态</th>
|
||||
<th className="px-4 py-3 font-medium">账号状态</th>
|
||||
<th className="px-4 py-3 text-right font-medium">加入时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[760px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">账号</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">角色</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">成员状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">账号状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">加入时间</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{members.map(({ account, membership }) => (
|
||||
<tr className="hover:bg-secondary/45" key={membership.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row className="hover:bg-secondary/45" key={membership.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<p className="font-medium">{account.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{account.email}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3">{membership.roleLabel}</td>
|
||||
<td className="px-4 py-3">{membership.status}</td>
|
||||
<td className="px-4 py-3">{account.status}</td>
|
||||
<td className="px-4 py-3 text-right text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{membership.roleLabel}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{membership.status}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{account.status}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-right text-muted-foreground">
|
||||
{new Date(membership.createdAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{members.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
暂无成员
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from "next/link";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { getCurrentAuthContext } from "@/modules/core/server/auth";
|
||||
import { getRoleDefinitions, hasPermission } from "@/modules/core/server/permissions";
|
||||
import { readData } from "@/modules/core/server/store";
|
||||
@@ -74,50 +75,50 @@ export default async function OrganizationsPage({ searchParams }: OrganizationsP
|
||||
total={filteredOrganizations.length}
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[820px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">机构</th>
|
||||
<th className="px-4 py-3 font-medium">Slug</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
<th className="px-4 py-3 text-right font-medium">创建时间</th>
|
||||
<th className="px-4 py-3 text-right font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[820px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">机构</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">Slug</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">创建时间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">操作</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{visibleOrganizations.map((organization) => (
|
||||
<tr className="table-row-enter hover:bg-secondary/45" key={organization.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row className="table-row-enter hover:bg-secondary/45" key={organization.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Link
|
||||
className="font-medium text-primary underline-offset-4 hover:underline"
|
||||
href={`/app/settings/organizations/${organization.id}`}
|
||||
>
|
||||
{organization.name}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{organization.slug}</td>
|
||||
<td className="px-4 py-3">{organization.status === "active" ? "启用" : "停用"}</td>
|
||||
<td className="px-4 py-3 text-right text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{organization.slug}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{organization.status === "active" ? "启用" : "停用"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-right text-muted-foreground">
|
||||
{new Date(organization.createdAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<OrganizationRowActions
|
||||
canInvite={canInviteMembers}
|
||||
organization={organization}
|
||||
roles={rolesByOrganizationId.get(organization.id) ?? []}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{visibleOrganizations.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
|
||||
暂无匹配机构
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { getCurrentAuthContext } from "@/modules/core/server/auth";
|
||||
import { getRoleDefinitions, hasPermission, PERMISSION_DEFINITIONS } from "@/modules/core/server/permissions";
|
||||
import { RoleManagementClient, RoleRowActions } from "@/modules/settings/components/RoleManagementClient";
|
||||
@@ -59,42 +60,42 @@ export default async function RolesPage({ searchParams }: RolesPageProps): Promi
|
||||
total={filteredRoles.length}
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[980px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">角色</th>
|
||||
<th className="px-4 py-3 font-medium">范围</th>
|
||||
<th className="px-4 py-3 font-medium">类型</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
<th className="px-4 py-3 font-medium">权限</th>
|
||||
<th className="px-4 py-3 text-right font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[980px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">角色</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">范围</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">类型</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">权限</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">操作</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{visibleRoles.map((role) => (
|
||||
<tr className="table-row-enter align-top hover:bg-secondary/45" key={role.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row className="table-row-enter align-top hover:bg-secondary/45" key={role.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<p className="font-medium">{role.label}</p>
|
||||
<p className="text-xs text-muted-foreground">{role.description}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3">{role.scope === "platform" ? "平台" : "机构"}</td>
|
||||
<td className="px-4 py-3">{role.isSystem ? "内置" : "自定义"}</td>
|
||||
<td className="px-4 py-3">{role.isEnabled ? "启用" : "停用"}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{role.permissions.join(", ") || "-"}</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{role.scope === "platform" ? "平台" : "机构"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{role.isSystem ? "内置" : "自定义"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{role.isEnabled ? "启用" : "停用"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{role.permissions.join(", ") || "-"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<RoleRowActions permissions={PERMISSION_DEFINITIONS} role={role} />
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{visibleRoles.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
暂无匹配角色
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { checkDatabaseConnection } from "@/modules/core/server/db";
|
||||
import { getCurrentAuthContext } from "@/modules/core/server/auth";
|
||||
import { hasPermission } from "@/modules/core/server/permissions";
|
||||
@@ -88,48 +89,48 @@ export default async function StatusPage({ searchParams }: StatusPageProps): Pro
|
||||
total={filteredIncidents.length}
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[960px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">标题</th>
|
||||
<th className="px-4 py-3 font-medium">严重级别</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
<th className="px-4 py-3 font-medium">来源</th>
|
||||
<th className="px-4 py-3 text-right font-medium">创建时间</th>
|
||||
<th className="px-4 py-3 text-right font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[960px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">标题</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">严重级别</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">来源</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">创建时间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">操作</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{visibleIncidents.map((incident) => (
|
||||
<tr className="table-row-enter align-top hover:bg-secondary/45" key={incident.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row className="table-row-enter align-top hover:bg-secondary/45" key={incident.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<p className="font-medium">{incident.title}</p>
|
||||
<p className="text-xs text-muted-foreground">{incident.description}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3">{incident.severity}</td>
|
||||
<td className="px-4 py-3">{incident.status}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{incident.source}</td>
|
||||
<td className="px-4 py-3 text-right text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{incident.severity}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{incident.status}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{incident.source}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-right text-muted-foreground">
|
||||
{new Date(incident.createdAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
{canManageIncidents ? (
|
||||
<IncidentStatusActions incident={incident} />
|
||||
) : (
|
||||
<span className="block text-right text-xs text-muted-foreground">只读</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{visibleIncidents.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
暂无匹配故障
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import { getCurrentAuthContext, toPublicAccount } from "@/modules/core/server/auth";
|
||||
import { getRoleDefinitions, hasPermission } from "@/modules/core/server/permissions";
|
||||
import { readData } from "@/modules/core/server/store";
|
||||
@@ -91,21 +92,21 @@ export default async function UsersPage({ searchParams }: UsersPageProps): Promi
|
||||
total={filteredAccounts.length}
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[920px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">账号</th>
|
||||
<th className="px-4 py-3 font-medium">角色</th>
|
||||
<th className="px-4 py-3 font-medium">机构</th>
|
||||
<th className="px-4 py-3 font-medium">状态</th>
|
||||
<th className="px-4 py-3 text-right font-medium">创建时间</th>
|
||||
<th className="px-4 py-3 text-right font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[920px] text-sm">
|
||||
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<Table.Row>
|
||||
<Table.Head className="px-4 py-3 font-medium">账号</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">角色</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">机构</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">状态</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">创建时间</Table.Head>
|
||||
<Table.Head className="px-4 py-3 text-right font-medium">操作</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{visibleAccounts.map((account) => (
|
||||
<tr className="table-row-enter hover:bg-secondary/45" key={account.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row className="table-row-enter hover:bg-secondary/45" key={account.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<UserAvatar avatarUrl={account.avatarUrl} name={account.name} size="sm" />
|
||||
<div className="min-w-0">
|
||||
@@ -113,36 +114,36 @@ export default async function UsersPage({ searchParams }: UsersPageProps): Promi
|
||||
<p className="truncate text-xs text-muted-foreground">{account.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">{formatRoleLabel(account.role)}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{account.organization ?? "-"}</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{formatRoleLabel(account.role)}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{account.organization ?? "-"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={account.status === "active" ? "success" : account.status === "pending" ? "warning" : "danger"}>
|
||||
{account.status === "active" ? "启用" : account.status === "pending" ? "待审批" : "停用"}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-right text-muted-foreground">
|
||||
{new Date(account.createdAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<UserAccountActions
|
||||
account={account}
|
||||
currentAccountId={context.account.id}
|
||||
organizations={data.organizations}
|
||||
roles={roles}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{visibleAccounts.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
|
||||
暂无匹配账号
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
@import "@cloudflare/kumo/styles";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@@ -49,6 +50,15 @@
|
||||
--border: oklch(0.89 0.016 151);
|
||||
--input: oklch(0.89 0.016 151);
|
||||
--ring: oklch(0.54 0.11 155);
|
||||
--color-kumo-brand: var(--primary);
|
||||
--color-kumo-brand-hover: oklch(0.42 0.12 155);
|
||||
--color-kumo-focus: var(--ring);
|
||||
--color-kumo-link: var(--primary);
|
||||
--text-color-kumo-brand: var(--primary);
|
||||
--text-color-kumo-link: var(--primary);
|
||||
--color-kumo-success: oklch(0.52 0.13 155);
|
||||
--color-kumo-success-tint: oklch(0.93 0.045 152);
|
||||
--color-kumo-badge-green: oklch(0.48 0.11 155);
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
Reference in New Issue
Block a user