feat: adopt kumo green ui system

This commit is contained in:
2026-07-02 07:12:31 -07:00
parent 4bb4312b08
commit 0f0bd8813d
32 changed files with 1453 additions and 796 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
* {