feat: adopt kumo green ui system
This commit is contained in:
@@ -97,6 +97,26 @@ export function InteractiveWidget({ initialData }: { initialData: Data }) {
|
||||
|
||||
## Semantic HTML
|
||||
|
||||
## Kumo UI Convention
|
||||
|
||||
Use `components/ui/*` as the project-owned adapter layer for Cloudflare Kumo components.
|
||||
Feature modules and route files should import `Button`, `Input`, `Select`, `Textarea`, `Table`,
|
||||
`Checkbox`, `Badge`, `Card`, and `Dialog` from `@/components/ui/...` instead of importing
|
||||
`@cloudflare/kumo` directly.
|
||||
|
||||
```typescript
|
||||
// Good: project adapter keeps Kumo API differences local
|
||||
import { Select } from "@/components/ui/select";
|
||||
import { Table } from "@/components/ui/table";
|
||||
|
||||
// Avoid: feature code should not hand-style native controls
|
||||
<select name="roleId" />
|
||||
<table />
|
||||
```
|
||||
|
||||
Visible form controls and data tables should use the Kumo-backed adapters. Native hidden
|
||||
inputs are acceptable only inside adapters when needed to preserve FormData semantics.
|
||||
|
||||
### Use Proper Elements
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -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">
|
||||
<LinkButton href="/app/settings/organizations" size="sm" variant="outline">
|
||||
<ArrowLeft className="size-4" aria-hidden="true" />
|
||||
返回机构列表
|
||||
</Link>
|
||||
</Button>
|
||||
</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);
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { Badge as KumoBadge } from "@cloudflare/kumo/components/badge";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
type KumoBadgeProps = React.ComponentProps<typeof KumoBadge>;
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-md border px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border-transparent bg-primary text-primary-foreground",
|
||||
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
||||
outline: "text-foreground",
|
||||
success: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||
warning: "border-amber-200 bg-amber-50 text-amber-700",
|
||||
danger: "border-red-200 bg-red-50 text-red-700",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
type LegacyBadgeVariant = "default" | "secondary" | "outline" | "success" | "warning" | "danger";
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
const variantMap: Record<LegacyBadgeVariant, KumoBadgeProps["variant"]> = {
|
||||
default: "green",
|
||||
secondary: "secondary",
|
||||
outline: "outline",
|
||||
success: "success",
|
||||
warning: "warning",
|
||||
danger: "error",
|
||||
};
|
||||
|
||||
export function Badge({ className, variant, ...props }: BadgeProps): React.ReactElement {
|
||||
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||
export type BadgeProps = Omit<KumoBadgeProps, "variant" | "children"> & {
|
||||
variant?: LegacyBadgeVariant | KumoBadgeProps["variant"];
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function Badge({ variant = "default", ...props }: BadgeProps): React.ReactElement {
|
||||
const mappedVariant = variant in variantMap ? variantMap[variant as LegacyBadgeVariant] : (variant as KumoBadgeProps["variant"]);
|
||||
|
||||
return <KumoBadge variant={mappedVariant} {...(props as KumoBadgeProps)} />;
|
||||
}
|
||||
|
||||
@@ -1,50 +1,59 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import {
|
||||
Button as KumoButton,
|
||||
LinkButton as KumoLinkButton,
|
||||
type ButtonProps as KumoButtonProps,
|
||||
type LinkButtonProps as KumoLinkButtonProps,
|
||||
} from "@cloudflare/kumo/components/button";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive";
|
||||
type ButtonSize = "default" | "sm" | "lg" | "icon";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex min-h-11 shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
outline: "border bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
},
|
||||
size: {
|
||||
default: "h-11 px-4 py-2",
|
||||
sm: "h-9 min-h-9 px-3",
|
||||
lg: "h-12 px-6",
|
||||
icon: "h-11 w-11",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
const variantMap: Record<ButtonVariant, KumoButtonProps["variant"]> = {
|
||||
default: "primary",
|
||||
secondary: "secondary",
|
||||
outline: "outline",
|
||||
ghost: "ghost",
|
||||
destructive: "destructive",
|
||||
};
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
const sizeMap: Record<ButtonSize, KumoButtonProps["size"]> = {
|
||||
default: "base",
|
||||
sm: "sm",
|
||||
lg: "lg",
|
||||
icon: "base",
|
||||
};
|
||||
|
||||
export function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: ButtonProps): React.ReactElement {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
export type ButtonProps = Omit<KumoButtonProps, "variant" | "size" | "shape"> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
};
|
||||
|
||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
||||
}
|
||||
export type LinkButtonProps = Omit<KumoLinkButtonProps, "variant" | "size"> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: Exclude<ButtonSize, "icon">;
|
||||
};
|
||||
|
||||
export { buttonVariants };
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||
{ variant = "default", size = "default", ...props },
|
||||
ref,
|
||||
): React.ReactElement {
|
||||
const kumoProps = {
|
||||
ref,
|
||||
shape: size === "icon" ? "square" : "base",
|
||||
size: sizeMap[size],
|
||||
variant: variantMap[variant],
|
||||
...props,
|
||||
} as KumoButtonProps & React.RefAttributes<HTMLButtonElement>;
|
||||
|
||||
return (
|
||||
<KumoButton {...kumoProps} />
|
||||
);
|
||||
});
|
||||
|
||||
export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>(function LinkButton(
|
||||
{ variant = "default", size = "default", ...props },
|
||||
ref,
|
||||
): React.ReactElement {
|
||||
return <KumoLinkButton ref={ref} size={sizeMap[size]} variant={variantMap[variant]} {...props} />;
|
||||
});
|
||||
|
||||
@@ -1,43 +1,29 @@
|
||||
import * as React from "react";
|
||||
import { LayerCard } from "@cloudflare/kumo/components/layer-card";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function Card({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||
export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||
<LayerCard
|
||||
className={cn("rounded-lg border border-kumo-line bg-kumo-base text-card-foreground shadow-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardHeader({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||
return <div className={cn("flex flex-col gap-1.5 p-5", className)} {...props} />;
|
||||
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||
return <LayerCard.Secondary className={cn("flex flex-col gap-1.5 p-5", className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLHeadingElement>): React.ReactElement {
|
||||
return <h3 className={cn("text-base font-semibold leading-none", className)} {...props} />;
|
||||
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): React.ReactElement {
|
||||
return <h3 className={cn("text-base font-semibold leading-none text-kumo-strong", className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLParagraphElement>): React.ReactElement {
|
||||
return <p className={cn("text-sm text-muted-foreground", className)} {...props} />;
|
||||
export function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>): React.ReactElement {
|
||||
return <p className={cn("text-sm text-kumo-subtle", className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardContent({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||
return <div className={cn("p-5 pt-0", className)} {...props} />;
|
||||
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||
return <LayerCard.Primary className={cn("p-5 pt-0", className)} {...props} />;
|
||||
}
|
||||
|
||||
39
components/ui/checkbox.tsx
Normal file
39
components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Checkbox as KumoCheckbox, type CheckboxProps as KumoCheckboxProps } from "@cloudflare/kumo/components/checkbox";
|
||||
|
||||
type CheckboxProps = Omit<KumoCheckboxProps, "checked" | "onCheckedChange"> & {
|
||||
checked?: boolean;
|
||||
defaultChecked?: boolean;
|
||||
onCheckedChange?: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
export function Checkbox({
|
||||
checked,
|
||||
defaultChecked = false,
|
||||
name,
|
||||
onCheckedChange,
|
||||
...props
|
||||
}: CheckboxProps): React.ReactElement {
|
||||
const [uncontrolledChecked, setUncontrolledChecked] = React.useState(defaultChecked);
|
||||
const isControlled = checked !== undefined;
|
||||
const currentChecked = isControlled ? checked : uncontrolledChecked;
|
||||
|
||||
return (
|
||||
<>
|
||||
<KumoCheckbox
|
||||
checked={currentChecked}
|
||||
onCheckedChange={(nextChecked) => {
|
||||
const booleanValue = Boolean(nextChecked);
|
||||
if (!isControlled) {
|
||||
setUncontrolledChecked(booleanValue);
|
||||
}
|
||||
onCheckedChange?.(booleanValue);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
{name && currentChecked ? <input name={name} type="hidden" value="on" /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { Dialog as KumoDialog } from "@cloudflare/kumo/components/dialog";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type DialogProps = {
|
||||
@@ -21,38 +25,35 @@ export function Dialog({
|
||||
footer,
|
||||
onClose,
|
||||
className,
|
||||
}: DialogProps): React.ReactElement | null {
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}: DialogProps): React.ReactElement {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/32 p-3 backdrop-blur-sm sm:items-center">
|
||||
<section
|
||||
aria-modal="true"
|
||||
<KumoDialog.Root
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
open={open}
|
||||
>
|
||||
<KumoDialog
|
||||
className={cn(
|
||||
"flex max-h-[92svh] w-full max-w-3xl flex-col overflow-hidden rounded-lg border bg-card shadow-xl",
|
||||
"flex max-h-[92svh] w-[min(calc(100vw-1.5rem),48rem)] flex-col overflow-hidden rounded-lg border border-kumo-line bg-kumo-base p-0 shadow-xl",
|
||||
className,
|
||||
)}
|
||||
role="dialog"
|
||||
size="xl"
|
||||
>
|
||||
<header className="flex shrink-0 items-start justify-between gap-4 border-b p-5">
|
||||
<header className="flex shrink-0 items-start justify-between gap-4 border-b border-kumo-line p-5">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-lg font-semibold">{title}</h2>
|
||||
{description ? <p className="mt-1 text-sm text-muted-foreground">{description}</p> : null}
|
||||
<KumoDialog.Title>{title}</KumoDialog.Title>
|
||||
{description ? <KumoDialog.Description className="mt-1">{description}</KumoDialog.Description> : null}
|
||||
</div>
|
||||
<button
|
||||
aria-label="关闭弹窗"
|
||||
className="inline-flex size-10 shrink-0 items-center justify-center rounded-md border bg-background text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
<Button aria-label="关闭弹窗" onClick={onClose} size="icon" type="button" variant="outline">
|
||||
<X className="size-4" aria-hidden="true" />
|
||||
</button>
|
||||
</Button>
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-5">{children}</div>
|
||||
{footer ? <footer className="shrink-0 border-t bg-secondary/35 p-4">{footer}</footer> : null}
|
||||
</section>
|
||||
</div>
|
||||
{footer ? <footer className="shrink-0 border-t border-kumo-line bg-kumo-recessed p-4">{footer}</footer> : null}
|
||||
</KumoDialog>
|
||||
</KumoDialog.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export function Input({ className, type, ...props }: InputProps): React.ReactElement {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export { Input, Textarea, type InputProps, type InputAreaProps as TextareaProps } from "@cloudflare/kumo/components/input";
|
||||
|
||||
49
components/ui/select.tsx
Normal file
49
components/ui/select.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Select as KumoSelect } from "@cloudflare/kumo/components/select";
|
||||
|
||||
export type SelectOption = {
|
||||
value: string;
|
||||
label: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
"aria-label"?: string;
|
||||
className?: string;
|
||||
defaultValue?: string;
|
||||
disabled?: boolean;
|
||||
label?: React.ReactNode;
|
||||
name?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
options: SelectOption[];
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
export function Select({
|
||||
onValueChange,
|
||||
options,
|
||||
placeholder,
|
||||
value,
|
||||
defaultValue,
|
||||
...props
|
||||
}: SelectProps): React.ReactElement {
|
||||
return (
|
||||
<KumoSelect<string>
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={(nextValue) => onValueChange?.(String(nextValue))}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
{...props}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<KumoSelect.Option key={option.value} disabled={option.disabled} value={option.value}>
|
||||
{option.label}
|
||||
</KumoSelect.Option>
|
||||
))}
|
||||
</KumoSelect>
|
||||
);
|
||||
}
|
||||
1
components/ui/table.tsx
Normal file
1
components/ui/table.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { Table, type KumoTableLayout, type KumoTableRowVariant } from "@cloudflare/kumo/components/table";
|
||||
@@ -9,6 +9,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Organization } from "@/modules/core/types";
|
||||
|
||||
@@ -206,17 +207,14 @@ function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement {
|
||||
{mode === "register" && !invitationToken && organizations.length > 0 ? (
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm font-medium">申请加入机构</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="申请加入机构"
|
||||
name="organizationId"
|
||||
>
|
||||
<option value="">暂不加入机构</option>
|
||||
{organizations.map((organization) => (
|
||||
<option key={organization.id} value={organization.id}>
|
||||
{organization.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={[
|
||||
{ value: "", label: "暂不加入机构" },
|
||||
...organizations.map((organization) => ({ value: organization.id, label: organization.name })),
|
||||
]}
|
||||
/>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { Clock3, MailCheck, ShieldCheck } 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 { SignOutButton } from "@/modules/auth/components/SignOutButton";
|
||||
import type { PublicAccount } from "@/modules/core/types";
|
||||
@@ -52,9 +51,9 @@ export function PendingAssignment({ account }: PendingAssignmentProps): React.Re
|
||||
<p className="mt-1 text-xs text-muted-foreground">分配角色后重新进入即可使用。</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild className="w-fit" variant="outline">
|
||||
<Link href="/app/dashboard">刷新状态</Link>
|
||||
</Button>
|
||||
<LinkButton className="w-fit" href="/app/dashboard" variant="outline">
|
||||
刷新状态
|
||||
</LinkButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { LucideIcon } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Table } from "@/components/ui/table";
|
||||
|
||||
type Metric = {
|
||||
label: string;
|
||||
@@ -112,30 +113,30 @@ export function DashboardHome(): React.ReactElement {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<table className="w-full 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 text-right font-medium">截止</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full 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 text-right font-medium">截止</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{taskRows.map((task) => (
|
||||
<tr key={task.name}>
|
||||
<td className="px-4 py-3 font-medium">{task.name}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{task.owner}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={task.name}>
|
||||
<Table.Cell className="px-4 py-3 font-medium">{task.name}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{task.owner}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={task.status === "需复核" ? "warning" : "secondary"}>
|
||||
{task.status}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-muted-foreground">{task.due}</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-right text-muted-foreground">{task.due}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -7,7 +7,9 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Input, Textarea } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Permission } from "@/modules/core/types";
|
||||
import type { CareLevel, Elder, ElderInput, ElderStatus } from "@/modules/elders/types";
|
||||
@@ -66,6 +68,20 @@ function getRiskLabel(elder: Elder): { label: string; variant: "danger" | "warni
|
||||
return { label: "绿色稳定", variant: "success" };
|
||||
}
|
||||
|
||||
const statusOptions = [
|
||||
{ value: ALL_STATUS, label: "全部状态" },
|
||||
...Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => ({ value, label })),
|
||||
];
|
||||
|
||||
const careLevelOptions = [
|
||||
{ value: ALL_CARE_LEVELS, label: "全部照护" },
|
||||
...Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => ({ value, label })),
|
||||
];
|
||||
|
||||
const genderOptions = Object.entries(GENDER_LABELS).map(([value, label]) => ({ value, label }));
|
||||
const formCareLevelOptions = Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => ({ value, label }));
|
||||
const formStatusOptions = Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => ({ value, label }));
|
||||
|
||||
function matchesFilters(
|
||||
elder: Elder,
|
||||
query: string,
|
||||
@@ -291,33 +307,23 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
|
||||
value={query}
|
||||
/>
|
||||
</label>
|
||||
<label className="relative block">
|
||||
<div className="relative block">
|
||||
<Filter className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-9 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onChange={(event) => updateStatusFilter(event.target.value)}
|
||||
<Select
|
||||
aria-label="按老人状态筛选"
|
||||
className="w-full pl-9"
|
||||
onValueChange={updateStatusFilter}
|
||||
options={statusOptions}
|
||||
value={statusFilter}
|
||||
>
|
||||
<option value={ALL_STATUS}>全部状态</option>
|
||||
{Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onChange={(event) => updateCareLevelFilter(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
aria-label="按照护等级筛选"
|
||||
className="w-full"
|
||||
onValueChange={updateCareLevelFilter}
|
||||
options={careLevelOptions}
|
||||
value={careLevelFilter}
|
||||
>
|
||||
<option value={ALL_CARE_LEVELS}>全部照护</option>
|
||||
{Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{message ? (
|
||||
@@ -328,46 +334,46 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<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 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 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">
|
||||
{visibleElders.map((elder) => {
|
||||
const risk = getRiskLabel(elder);
|
||||
|
||||
return (
|
||||
<tr key={elder.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={elder.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<p className="font-medium">{elder.name}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{GENDER_LABELS[elder.gender]} · {elder.age} 岁
|
||||
</p>
|
||||
</td>
|
||||
<td className="px-4 py-3">{CARE_LEVEL_LABELS[elder.careLevel]}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{CARE_LEVEL_LABELS[elder.careLevel]}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">
|
||||
{elder.room}-{elder.bed}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">
|
||||
{elder.primaryContact} / {elder.phone}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={elder.status === "active" ? "success" : "secondary"}>
|
||||
{ELDER_STATUS_LABELS[elder.status]}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={risk.variant}>{risk.label}</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
disabled={!canUpdate}
|
||||
@@ -390,19 +396,19 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
{visibleElders.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={7}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={7}>
|
||||
暂无匹配老人档案
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -444,20 +450,15 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
|
||||
<Input value={form.name} onChange={(event) => updateField("name", event.target.value)} />
|
||||
</label>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="grid gap-1.5 text-sm font-medium">
|
||||
<div className="grid gap-1.5 text-sm font-medium">
|
||||
性别
|
||||
<select
|
||||
className="h-11 rounded-md border bg-background px-3 text-sm"
|
||||
onChange={(event) => updateField("gender", event.target.value as ElderInput["gender"])}
|
||||
<Select
|
||||
aria-label="性别"
|
||||
onValueChange={(value) => updateField("gender", value as ElderInput["gender"])}
|
||||
options={genderOptions}
|
||||
value={form.gender}
|
||||
>
|
||||
{Object.entries(GENDER_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
/>
|
||||
</div>
|
||||
<label className="grid gap-1.5 text-sm font-medium">
|
||||
年龄
|
||||
<Input
|
||||
@@ -470,34 +471,24 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="grid gap-1.5 text-sm font-medium">
|
||||
<div className="grid gap-1.5 text-sm font-medium">
|
||||
护理等级
|
||||
<select
|
||||
className="h-11 rounded-md border bg-background px-3 text-sm"
|
||||
onChange={(event) => updateField("careLevel", event.target.value as ElderInput["careLevel"])}
|
||||
<Select
|
||||
aria-label="护理等级"
|
||||
onValueChange={(value) => updateField("careLevel", value as ElderInput["careLevel"])}
|
||||
options={formCareLevelOptions}
|
||||
value={form.careLevel}
|
||||
>
|
||||
{Object.entries(CARE_LEVEL_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="grid gap-1.5 text-sm font-medium">
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-1.5 text-sm font-medium">
|
||||
状态
|
||||
<select
|
||||
className="h-11 rounded-md border bg-background px-3 text-sm"
|
||||
onChange={(event) => updateField("status", event.target.value as ElderInput["status"])}
|
||||
<Select
|
||||
aria-label="状态"
|
||||
onValueChange={(value) => updateField("status", value as ElderInput["status"])}
|
||||
options={formStatusOptions}
|
||||
value={form.status}
|
||||
>
|
||||
{Object.entries(ELDER_STATUS_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="grid gap-1.5 text-sm font-medium">
|
||||
@@ -519,8 +510,8 @@ export function EldersClient({ initialElders, permissions }: EldersClientProps):
|
||||
</label>
|
||||
<label className="grid gap-1.5 text-sm font-medium">
|
||||
健康备注
|
||||
<textarea
|
||||
className="min-h-24 rounded-md border bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Textarea
|
||||
className="min-h-24"
|
||||
onChange={(event) => updateField("medicalNotes", event.target.value)}
|
||||
value={form.medicalNotes}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRouter } from "next/navigation";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { SystemSettings } from "@/modules/core/types";
|
||||
@@ -69,7 +70,7 @@ export function GlobalSettingsClient({ settings }: GlobalSettingsClientProps): R
|
||||
<span className="block font-medium">开放公开注册</span>
|
||||
<span className="block text-xs text-muted-foreground">关闭后普通注册不可用;邀请链接仍可使用。</span>
|
||||
</span>
|
||||
<input defaultChecked={settings.registrationEnabled} name="registrationEnabled" type="checkbox" />
|
||||
<Checkbox aria-label="开放公开注册" defaultChecked={settings.registrationEnabled} name="registrationEnabled" />
|
||||
</label>
|
||||
|
||||
<label className="flex items-center justify-between gap-4 rounded-md border p-3 text-sm">
|
||||
@@ -77,7 +78,7 @@ export function GlobalSettingsClient({ settings }: GlobalSettingsClientProps): R
|
||||
<span className="block font-medium">启用 OIDC 登录</span>
|
||||
<span className="block text-xs text-muted-foreground">作为平台级总开关,机构级 OIDC 配置仍可单独管理。</span>
|
||||
</span>
|
||||
<input defaultChecked={settings.oidcEnabled} name="oidcEnabled" type="checkbox" />
|
||||
<Checkbox aria-label="启用 OIDC 登录" defaultChecked={settings.oidcEnabled} name="oidcEnabled" />
|
||||
</label>
|
||||
|
||||
<label className="flex items-center justify-between gap-4 rounded-md border p-3 text-sm">
|
||||
@@ -85,7 +86,7 @@ export function GlobalSettingsClient({ settings }: GlobalSettingsClientProps): R
|
||||
<span className="block font-medium">OIDC 自动开户</span>
|
||||
<span className="block text-xs text-muted-foreground">首次 OIDC 登录时按声明信息创建账号,默认进入等待分配。</span>
|
||||
</span>
|
||||
<input defaultChecked={settings.oidcAutoProvision} name="oidcAutoProvision" type="checkbox" />
|
||||
<Checkbox aria-label="OIDC 自动开户" defaultChecked={settings.oidcAutoProvision} name="oidcAutoProvision" />
|
||||
</label>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -7,7 +7,9 @@ import { useRouter } from "next/navigation";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Table } from "@/components/ui/table";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Organization, OrganizationInvitation, RoleDefinition } from "@/modules/core/types";
|
||||
import { getInviteHref, OrganizationInviteDialog } from "@/modules/settings/components/OrganizationInviteDialog";
|
||||
@@ -74,7 +76,11 @@ export function OrganizationDetailClient({
|
||||
<span className="block font-medium">开放公开注册</span>
|
||||
<span className="block text-xs text-muted-foreground">关闭后,注册页不再展示该机构;邀请链接仍可使用。</span>
|
||||
</span>
|
||||
<input defaultChecked={organization.registrationEnabled} name="registrationEnabled" type="checkbox" />
|
||||
<Checkbox
|
||||
aria-label="开放公开注册"
|
||||
defaultChecked={organization.registrationEnabled}
|
||||
name="registrationEnabled"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center justify-between gap-4 rounded-md border p-3 text-sm">
|
||||
@@ -82,7 +88,7 @@ export function OrganizationDetailClient({
|
||||
<span className="block font-medium">启用 OIDC 登录</span>
|
||||
<span className="block text-xs text-muted-foreground">保存标准 OIDC 配置,后续登录入口按该开关展示。</span>
|
||||
</span>
|
||||
<input defaultChecked={organization.oidcEnabled} name="oidcEnabled" type="checkbox" />
|
||||
<Checkbox aria-label="启用 OIDC 登录" defaultChecked={organization.oidcEnabled} name="oidcEnabled" />
|
||||
</label>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
@@ -126,7 +132,11 @@ export function OrganizationDetailClient({
|
||||
<span className="block font-medium">OIDC 自动开户</span>
|
||||
<span className="block text-xs text-muted-foreground">首次 OIDC 登录时自动创建账号并进入本机构。</span>
|
||||
</span>
|
||||
<input defaultChecked={organization.oidcAutoProvision} name="oidcAutoProvision" type="checkbox" />
|
||||
<Checkbox
|
||||
aria-label="OIDC 自动开户"
|
||||
defaultChecked={organization.oidcAutoProvision}
|
||||
name="oidcAutoProvision"
|
||||
/>
|
||||
</label>
|
||||
|
||||
{settingsMessage ? (
|
||||
@@ -156,28 +166,28 @@ export function OrganizationDetailClient({
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[720px] 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-[720px] 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">
|
||||
{invitations.map((invitation) => (
|
||||
<tr className="hover:bg-secondary/45" key={invitation.id}>
|
||||
<td className="px-4 py-3">{invitation.email || "通用邀请"}</td>
|
||||
<td className="px-4 py-3">{invitation.roleLabel}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row className="hover:bg-secondary/45" key={invitation.id}>
|
||||
<Table.Cell className="px-4 py-3">{invitation.email || "通用邀请"}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{invitation.roleLabel}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={invitation.status === "active" ? "success" : "secondary"}>{invitation.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(invitation.expiresAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-right">
|
||||
<Button
|
||||
onClick={() => void navigator.clipboard.writeText(getInviteHref(invitation.token))}
|
||||
size="sm"
|
||||
@@ -187,18 +197,18 @@ export function OrganizationDetailClient({
|
||||
<Copy className="size-4" aria-hidden="true" />
|
||||
复制
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{invitations.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>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<LinkIcon className="size-4" aria-hidden="true" />
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Organization, OrganizationInvitation, RoleDefinition } from "@/modules/core/types";
|
||||
|
||||
@@ -91,20 +92,15 @@ export function OrganizationInviteDialog({
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">角色</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="邀请角色"
|
||||
defaultValue={defaultRoleId}
|
||||
disabled={organizationRoles.length === 0}
|
||||
key={organization.id}
|
||||
name="roleId"
|
||||
options={organizationRoles.map((role) => ({ value: role.id, label: role.label }))}
|
||||
required
|
||||
>
|
||||
{organizationRoles.map((role) => (
|
||||
<option key={role.id} value={role.id}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</label>
|
||||
{message ? (
|
||||
<p className="rounded-md bg-secondary px-3 py-2 text-sm text-secondary-foreground" role="status">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Organization, RoleDefinition } from "@/modules/core/types";
|
||||
import { OrganizationInviteDialog } from "@/modules/settings/components/OrganizationInviteDialog";
|
||||
@@ -204,14 +205,15 @@ export function OrganizationRowActions({
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">状态</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="机构状态"
|
||||
defaultValue={organization.status}
|
||||
name="status"
|
||||
>
|
||||
<option value="active">启用</option>
|
||||
<option value="disabled">停用</option>
|
||||
</select>
|
||||
options={[
|
||||
{ value: "active", label: "启用" },
|
||||
{ value: "disabled", label: "停用" },
|
||||
]}
|
||||
/>
|
||||
</label>
|
||||
{message ? (
|
||||
<p className="rounded-md bg-secondary px-3 py-2 text-sm text-secondary-foreground" role="status">
|
||||
|
||||
@@ -6,8 +6,10 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { Permission, RoleDefinition } from "@/modules/core/types";
|
||||
|
||||
@@ -101,11 +103,10 @@ function PermissionTree({
|
||||
<div className="rounded-md border bg-card" key={group.category}>
|
||||
<label className="flex items-center justify-between gap-3 border-b px-3 py-3 text-sm">
|
||||
<span className="flex min-w-0 items-center gap-3">
|
||||
<input
|
||||
<Checkbox
|
||||
aria-label={`切换 ${group.category} 权限`}
|
||||
checked={isAllSelected}
|
||||
className="size-4 rounded border-input"
|
||||
onChange={(event) => toggleCategory(group.permissions, event.target.checked)}
|
||||
type="checkbox"
|
||||
onCheckedChange={(checked) => toggleCategory(group.permissions, checked)}
|
||||
/>
|
||||
<span>
|
||||
<span className="block font-medium">{group.category}</span>
|
||||
@@ -122,11 +123,11 @@ function PermissionTree({
|
||||
className="flex min-h-16 items-start gap-3 rounded-md border bg-background p-3 text-sm transition-colors hover:bg-secondary/50"
|
||||
key={permission.id}
|
||||
>
|
||||
<input
|
||||
<Checkbox
|
||||
aria-label={`切换 ${permission.label}`}
|
||||
checked={selectedSet.has(permission.id)}
|
||||
className="mt-1 size-4 rounded border-input"
|
||||
onChange={(event) => togglePermission(permission.id, event.target.checked)}
|
||||
type="checkbox"
|
||||
className="mt-1"
|
||||
onCheckedChange={(checked) => togglePermission(permission.id, checked)}
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block font-medium">{permission.label}</span>
|
||||
@@ -366,14 +367,15 @@ export function RoleRowActions({ permissions, role }: RoleRowActionsProps): Reac
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">状态</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="角色状态"
|
||||
defaultValue={String(role.isEnabled)}
|
||||
name="isEnabled"
|
||||
>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">停用</option>
|
||||
</select>
|
||||
options={[
|
||||
{ value: "true", label: "启用" },
|
||||
{ value: "false", label: "停用" },
|
||||
]}
|
||||
/>
|
||||
</label>
|
||||
<PermissionTree
|
||||
onChange={setSelectedPermissions}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Activity, Building2, Server, ShieldCheck, Users } 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 type {
|
||||
AuditLog,
|
||||
JoinRequest,
|
||||
@@ -191,39 +192,39 @@ export function SettingsOverview({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[560px] 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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
<Table className="w-full min-w-[560px] 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.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{organizations.map((organization) => (
|
||||
<tr key={organization.id}>
|
||||
<td className="px-4 py-3 font-medium">{organization.name}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{organization.slug}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={organization.id}>
|
||||
<Table.Cell className="px-4 py-3 font-medium">{organization.name}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{organization.slug}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={organization.status === "active" ? "success" : "danger"}>
|
||||
{organization.status === "active" ? "启用" : "停用"}
|
||||
</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(organization.createdAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{organizations.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={4}>
|
||||
<Table.Row>
|
||||
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={4}>
|
||||
暂无机构
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -285,35 +286,35 @@ export function SettingsOverview({
|
||||
</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 text-right 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 text-right font-medium">创建时间</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body className="divide-y bg-card">
|
||||
{accounts.map((account) => (
|
||||
<tr key={account.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={account.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">{formatRoleLabel(account.role)}</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">
|
||||
<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>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -325,41 +326,41 @@ export function SettingsOverview({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[720px] text-sm">
|
||||
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">账号 ID</th>
|
||||
<th className="px-4 py-3 font-medium">机构 ID</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-[720px] 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">账号 ID</Table.Head>
|
||||
<Table.Head className="px-4 py-3 font-medium">机构 ID</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">
|
||||
{memberships.map((membership) => (
|
||||
<tr key={membership.id}>
|
||||
<td className="px-4 py-3 text-muted-foreground">{membership.accountId.slice(0, 8)}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{membership.organizationId.slice(0, 8)}</td>
|
||||
<td className="px-4 py-3">{membership.roleLabel}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={membership.id}>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{membership.accountId.slice(0, 8)}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{membership.organizationId.slice(0, 8)}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{membership.roleLabel}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={membership.status === "active" ? "success" : membership.status === "pending" ? "warning" : "danger"}>
|
||||
{membership.status}
|
||||
</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(membership.createdAt).toLocaleString("zh-CN")}
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{memberships.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>
|
||||
@@ -370,30 +371,30 @@ export function SettingsOverview({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto rounded-md border">
|
||||
<table className="w-full min-w-[860px] 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-[860px] 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">
|
||||
{auditLogs.map((log) => (
|
||||
<tr key={log.id}>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
<Table.Row key={log.id}>
|
||||
<Table.Cell 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.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">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge
|
||||
variant={
|
||||
log.result === "success" ? "success" : log.result === "denied" ? "warning" : "danger"
|
||||
@@ -401,19 +402,19 @@ export function SettingsOverview({
|
||||
>
|
||||
{log.result}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{log.reason}</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{log.reason}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{auditLogs.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,6 +1,4 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Button, LinkButton } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { buildPageHref } from "@/modules/settings/lib/pagination";
|
||||
|
||||
@@ -39,9 +37,9 @@ export function TableToolbar({
|
||||
上一页
|
||||
</Button>
|
||||
) : (
|
||||
<Button asChild size="sm" variant="outline">
|
||||
<Link href={buildPageHref(pathname, query, page - 1)}>上一页</Link>
|
||||
</Button>
|
||||
<LinkButton href={buildPageHref(pathname, query, page - 1)} size="sm" variant="outline">
|
||||
上一页
|
||||
</LinkButton>
|
||||
)}
|
||||
<span className="min-w-16 text-center">
|
||||
{page}/{pageCount}
|
||||
@@ -51,9 +49,9 @@ export function TableToolbar({
|
||||
下一页
|
||||
</Button>
|
||||
) : (
|
||||
<Button asChild size="sm" variant="outline">
|
||||
<Link href={buildPageHref(pathname, query, page + 1)}>下一页</Link>
|
||||
</Button>
|
||||
<LinkButton href={buildPageHref(pathname, query, page + 1)} size="sm" variant="outline">
|
||||
下一页
|
||||
</LinkButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import type { ApiResult } from "@/modules/core/server/api";
|
||||
import type { JoinRequest, Organization, PublicAccount, RoleDefinition } from "@/modules/core/types";
|
||||
|
||||
@@ -36,6 +37,7 @@ export function UserManagementClient({
|
||||
const router = useRouter();
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [selectedOrganizationId, setSelectedOrganizationId] = useState(() => getDefaultOrganizationId(organizations));
|
||||
const [reviewRoleByRequestId, setReviewRoleByRequestId] = useState<Record<string, string>>({});
|
||||
const [message, setMessage] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
@@ -169,6 +171,7 @@ export function UserManagementClient({
|
||||
{pendingJoinRequests.map((request) => {
|
||||
const requestRoles = getOrganizationRoles(roles, request.organizationId);
|
||||
const defaultRoleId = requestRoles.find((role) => role.key === "caregiver")?.id ?? requestRoles[0]?.id ?? "";
|
||||
const selectedRoleId = reviewRoleByRequestId[request.id] ?? defaultRoleId;
|
||||
|
||||
return (
|
||||
<div key={request.id} className="rounded-md border p-3">
|
||||
@@ -181,24 +184,18 @@ export function UserManagementClient({
|
||||
<Badge variant="warning">待审批</Badge>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2 sm:grid-cols-[1fr_auto_auto]">
|
||||
<select
|
||||
className="h-10 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
defaultValue={defaultRoleId}
|
||||
id={`role-${request.id}`}
|
||||
>
|
||||
{requestRoles.map((role) => (
|
||||
<option key={role.id} value={role.id}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Select
|
||||
aria-label={`${request.accountName} 审批角色`}
|
||||
disabled={requestRoles.length === 0}
|
||||
onValueChange={(roleId) =>
|
||||
setReviewRoleByRequestId((current) => ({ ...current, [request.id]: roleId }))
|
||||
}
|
||||
options={requestRoles.map((role) => ({ value: role.id, label: role.label }))}
|
||||
value={selectedRoleId}
|
||||
/>
|
||||
<Button
|
||||
disabled={isPending || !defaultRoleId || !canManageAccounts}
|
||||
onClick={() => {
|
||||
const element = document.getElementById(`role-${request.id}`);
|
||||
const roleId = element instanceof HTMLSelectElement ? element.value : defaultRoleId;
|
||||
void reviewJoinRequest(request.id, "approved", roleId);
|
||||
}}
|
||||
disabled={isPending || !selectedRoleId || !canManageAccounts}
|
||||
onClick={() => void reviewJoinRequest(request.id, "approved", selectedRoleId)}
|
||||
size="sm"
|
||||
type="button"
|
||||
>
|
||||
@@ -232,35 +229,26 @@ export function UserManagementClient({
|
||||
<form className="grid gap-3" onSubmit={handleCreateAccount}>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">机构</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="机构"
|
||||
name="organizationId"
|
||||
onChange={(event) => setSelectedOrganizationId(event.target.value)}
|
||||
onValueChange={setSelectedOrganizationId}
|
||||
options={organizations.map((organization) => ({ value: organization.id, label: organization.name }))}
|
||||
value={selectedOrganizationId}
|
||||
>
|
||||
{organizations.map((organization) => (
|
||||
<option key={organization.id} value={organization.id}>
|
||||
{organization.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">角色</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="角色"
|
||||
defaultValue={organizationRoles[0]?.id ?? ""}
|
||||
disabled={organizationRoles.length === 0}
|
||||
key={selectedOrganizationId}
|
||||
name="roleId"
|
||||
options={organizationRoles.map((role) => ({ value: role.id, label: role.label }))}
|
||||
required
|
||||
>
|
||||
{organizationRoles.map((role) => (
|
||||
<option key={role.id} value={role.id}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="grid gap-2 text-sm">
|
||||
@@ -424,47 +412,38 @@ export function UserAccountActions({
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">状态</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="账号状态"
|
||||
defaultValue={account.status}
|
||||
disabled={isCurrentAccount}
|
||||
name="status"
|
||||
>
|
||||
<option value="active">启用</option>
|
||||
<option value="disabled">停用</option>
|
||||
</select>
|
||||
options={[
|
||||
{ value: "active", label: "启用" },
|
||||
{ value: "disabled", label: "停用" },
|
||||
]}
|
||||
/>
|
||||
</label>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">机构</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="账号机构"
|
||||
name="organizationId"
|
||||
onChange={(event) => setSelectedOrganizationId(event.target.value)}
|
||||
onValueChange={setSelectedOrganizationId}
|
||||
options={organizations.map((organization) => ({ value: organization.id, label: organization.name }))}
|
||||
value={selectedOrganizationId}
|
||||
>
|
||||
{organizations.map((organization) => (
|
||||
<option key={organization.id} value={organization.id}>
|
||||
{organization.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="font-medium">角色</span>
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
<Select
|
||||
aria-label="账号角色"
|
||||
defaultValue={defaultRoleId}
|
||||
disabled={organizationRoles.length === 0}
|
||||
key={selectedOrganizationId}
|
||||
name="roleId"
|
||||
>
|
||||
{organizationRoles.map((role) => (
|
||||
<option key={role.id} value={role.id}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={organizationRoles.map((role) => ({ value: role.id, label: role.label }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{message ? (
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import { Table } from "@/components/ui/table";
|
||||
|
||||
export type CmsRecordTone = "success" | "warning" | "danger" | "secondary";
|
||||
|
||||
@@ -92,71 +94,67 @@ export function CmsModuleClient({ records }: CmsModuleClientProps): React.ReactE
|
||||
value={query}
|
||||
/>
|
||||
</label>
|
||||
<label className="relative block">
|
||||
<div className="relative block">
|
||||
<Filter className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<select
|
||||
className="h-11 w-full rounded-md border bg-background px-9 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onChange={(event) => updateStatus(event.target.value)}
|
||||
<Select
|
||||
aria-label="按状态筛选"
|
||||
className="w-full pl-9"
|
||||
onValueChange={updateStatus}
|
||||
options={statusOptions.map((option) => ({ value: option, label: option }))}
|
||||
value={status}
|
||||
>
|
||||
{statusOptions.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[860px] 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-[860px] 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">
|
||||
{visibleRecords.map((record) => (
|
||||
<tr key={record.id}>
|
||||
<td className="px-4 py-3">
|
||||
<Table.Row key={record.id}>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<p className="font-medium">{record.title}</p>
|
||||
<p className="mt-1 max-w-md truncate text-xs text-muted-foreground">{record.summary}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{record.category}</td>
|
||||
<td className="px-4 py-3">{record.owner}</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3 text-muted-foreground">{record.category}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">{record.owner}</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={record.statusTone}>{record.status}</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<Badge variant={record.priority === "高" ? "danger" : record.priority === "中" ? "warning" : "secondary"}>
|
||||
{record.priority}优先级
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
</Table.Cell>
|
||||
<Table.Cell className="px-4 py-3">
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={() => setSelectedRecord(record)} size="sm" type="button" variant="outline">
|
||||
<Eye className="size-4" aria-hidden="true" />
|
||||
详情
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
{visibleRecords.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>
|
||||
|
||||
<div className="flex flex-col gap-3 border-t p-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudflare/kumo": "^2.6.0",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
582
pnpm-lock.yaml
generated
582
pnpm-lock.yaml
generated
@@ -8,6 +8,12 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@cloudflare/kumo':
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0(@date-fns/tz@1.5.0)(@phosphor-icons/react@2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@phosphor-icons/react':
|
||||
specifier: ^2.1.10
|
||||
version: 2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.3
|
||||
version: 1.3.0(@types/react@19.2.17)(react@19.2.7)
|
||||
@@ -82,6 +88,55 @@ packages:
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@babel/runtime@7.29.7':
|
||||
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui/react@1.6.0':
|
||||
resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@date-fns/tz': ^1.2.0
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
date-fns: ^4.0.0
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@date-fns/tz':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
date-fns:
|
||||
optional: true
|
||||
|
||||
'@base-ui/utils@0.3.1':
|
||||
resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/kumo@2.6.0':
|
||||
resolution: {integrity: sha512-rcUUvhrtxI0veNJZLgKVSnxH/L0M48jtc4UoNhvu0l0RiRmjHORFTBYq/phFQyt7JGG3s98QPZc5w1eW+UQIOg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@phosphor-icons/react': ^2.1.10
|
||||
echarts: ^6.0.0
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
zod: ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
echarts:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@date-fns/tz@1.5.0':
|
||||
resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
|
||||
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
@@ -587,6 +642,21 @@ packages:
|
||||
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||
|
||||
'@floating-ui/dom@1.7.6':
|
||||
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.8':
|
||||
resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.11':
|
||||
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@@ -836,6 +906,13 @@ packages:
|
||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||
engines: {node: '>=12.4.0'}
|
||||
|
||||
'@phosphor-icons/react@2.1.10':
|
||||
resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>= 16.8'
|
||||
react-dom: '>= 16.8'
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.3':
|
||||
resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==}
|
||||
peerDependencies:
|
||||
@@ -860,9 +937,44 @@ packages:
|
||||
'@rushstack/eslint-patch@1.16.1':
|
||||
resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==}
|
||||
|
||||
'@shikijs/core@4.3.0':
|
||||
resolution: {integrity: sha512-EooU3i9F6IAE8kEu+AnGf9DFZWkQBZ+hJn3tLVbsH+61mtQiva5biai66fAA6nvFPXkLgvrh7BrR7YcJU83xQQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/engine-javascript@4.3.0':
|
||||
resolution: {integrity: sha512-hTv/KiFf2tpiqlACPiztGGurEARWIutB8YUhcrA1pUC7VzzwKO+g5crUocrLztrZ5ro5Z4hbXg7bYclETn3gSQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/engine-oniguruma@4.3.0':
|
||||
resolution: {integrity: sha512-1vMdN3gHfnKfLYwecUI2ITJI4RhHt96xEaJumVn7Heb0IlJ8WQMIH0Voak+2j22BpSNKdnOfB/pCTPnPm2gq7A==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/langs@4.3.0':
|
||||
resolution: {integrity: sha512-rnlqFbBRSys9bT4gl/5rw9RnS0W/I84ZldXPkO7cvlEMoV85TyF/aU01N7/NbSR776RNLjrJKjfFUXJR6wN1Cg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/primitive@4.3.0':
|
||||
resolution: {integrity: sha512-CPkz64PTa5diRW1ggzMZH9VM/du4RNChYgVtgqrFcgruvIybmCvySv8GkiHSczUHXYuuR8TdKEwFx+UnZMpgdg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/themes@4.3.0':
|
||||
resolution: {integrity: sha512-Avgt05YiT+Y3prjIc9lmQxhJzHBcCfR6cjiFW4OyaMBbt2A6trX5rfjUzx+Vj/mE9qpArYjatnqo9XPjQNW/AQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/types@4.3.0':
|
||||
resolution: {integrity: sha512-oc8b9U2SYvofKZk8e/737nIX0qwf6eV2vHFATeObAu7r+mUVpLs8Re0BmVkIjAWAYgkmG/CzLNo7rzuBzRu/wQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||
|
||||
'@tabby_ai/hijri-converter@1.0.5':
|
||||
resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@tailwindcss/node@4.3.2':
|
||||
resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==}
|
||||
|
||||
@@ -957,12 +1069,18 @@ packages:
|
||||
'@types/estree@1.0.9':
|
||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/json5@0.0.29':
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||
|
||||
'@types/node@22.20.0':
|
||||
resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==}
|
||||
|
||||
@@ -974,6 +1092,9 @@ packages:
|
||||
'@types/react@19.2.17':
|
||||
resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
|
||||
|
||||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.62.1':
|
||||
resolution: {integrity: sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1033,6 +1154,9 @@ packages:
|
||||
resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@ungap/structured-clone@1.3.2':
|
||||
resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==}
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.12.2':
|
||||
resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==}
|
||||
cpu: [arm]
|
||||
@@ -1258,10 +1382,19 @@ packages:
|
||||
caniuse-lite@1.0.30001800:
|
||||
resolution: {integrity: sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==}
|
||||
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
character-entities-html4@2.1.0:
|
||||
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
|
||||
|
||||
character-entities-legacy@3.0.0:
|
||||
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
@@ -1279,6 +1412,9 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -1304,6 +1440,12 @@ packages:
|
||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
date-fns-jalali@4.1.0-0:
|
||||
resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
|
||||
|
||||
date-fns@4.4.0:
|
||||
resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
@@ -1332,10 +1474,17 @@ packages:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
|
||||
doctrine@2.1.0:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1670,6 +1819,20 @@ packages:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
framer-motion@12.42.2:
|
||||
resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1754,6 +1917,15 @@ packages:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hast-util-to-html@9.0.5:
|
||||
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -2026,10 +2198,28 @@ packages:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mdast-util-to-hast@13.2.1:
|
||||
resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
|
||||
|
||||
merge2@1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
|
||||
|
||||
micromark-util-encode@2.0.1:
|
||||
resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
|
||||
|
||||
micromark-util-sanitize-uri@2.0.1:
|
||||
resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
|
||||
|
||||
micromark-util-symbol@2.0.1:
|
||||
resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
|
||||
|
||||
micromark-util-types@2.0.2:
|
||||
resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
|
||||
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
@@ -2044,6 +2234,26 @@ packages:
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
motion-dom@12.42.2:
|
||||
resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==}
|
||||
|
||||
motion-utils@12.39.0:
|
||||
resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
|
||||
|
||||
motion@12.42.2:
|
||||
resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -2117,6 +2327,12 @@ packages:
|
||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
oniguruma-parser@0.12.2:
|
||||
resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==}
|
||||
|
||||
oniguruma-to-es@4.3.6:
|
||||
resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2182,6 +2398,9 @@ packages:
|
||||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
property-information@7.2.0:
|
||||
resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2189,6 +2408,12 @@ packages:
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
react-day-picker@9.14.0:
|
||||
resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
react-dom@19.2.7:
|
||||
resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==}
|
||||
peerDependencies:
|
||||
@@ -2205,10 +2430,22 @@ packages:
|
||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
|
||||
|
||||
regex-utilities@2.3.0:
|
||||
resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
|
||||
|
||||
regex@6.1.0:
|
||||
resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
reselect@5.2.0:
|
||||
resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2276,6 +2513,10 @@ packages:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shiki@4.3.0:
|
||||
resolution: {integrity: sha512-NKKjWzR6LIGL3sXBrWDw9sDS9cxx42/DkysaNqJEeOWE8Kix5gpak0bc00OfDVEO4oyXSyz8+aRaqKoBD1yo7A==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
side-channel-list@1.0.1:
|
||||
resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2303,6 +2544,9 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
stable-hash@0.0.5:
|
||||
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
|
||||
|
||||
@@ -2333,6 +2577,9 @@ packages:
|
||||
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
stringify-entities@4.0.4:
|
||||
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
|
||||
|
||||
strip-bom@3.0.0:
|
||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2380,6 +2627,9 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
ts-api-utils@2.5.0:
|
||||
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
|
||||
engines: {node: '>=18.12'}
|
||||
@@ -2436,12 +2686,38 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
unist-util-is@6.0.1:
|
||||
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
|
||||
|
||||
unist-util-position@5.0.0:
|
||||
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
|
||||
|
||||
unist-util-stringify-position@4.0.0:
|
||||
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
|
||||
|
||||
unist-util-visit-parents@6.0.2:
|
||||
resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
|
||||
|
||||
unist-util-visit@5.1.0:
|
||||
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
|
||||
|
||||
unrs-resolver@1.12.2:
|
||||
resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==}
|
||||
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
use-sync-external-store@1.6.0:
|
||||
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
vfile-message@4.0.3:
|
||||
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
|
||||
|
||||
vfile@6.0.3:
|
||||
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2471,10 +2747,61 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@babel/runtime@7.29.7': {}
|
||||
|
||||
'@base-ui/react@1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@base-ui/utils': 0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@floating-ui/utils': 0.2.11
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
use-sync-external-store: 1.6.0(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@date-fns/tz': 1.5.0
|
||||
'@types/react': 19.2.17
|
||||
date-fns: 4.4.0
|
||||
|
||||
'@base-ui/utils@0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@floating-ui/utils': 0.2.11
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
reselect: 5.2.0
|
||||
use-sync-external-store: 1.6.0(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@cloudflare/kumo@2.6.0(@date-fns/tz@1.5.0)(@phosphor-icons/react@2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@base-ui/react': 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@phosphor-icons/react': 2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@shikijs/langs': 4.3.0
|
||||
'@shikijs/themes': 4.3.0
|
||||
clsx: 2.1.1
|
||||
motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-day-picker: 9.14.0(react@19.2.7)
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
shiki: 4.3.0
|
||||
tailwind-merge: 3.6.0
|
||||
transitivePeerDependencies:
|
||||
- '@date-fns/tz'
|
||||
- '@emotion/is-prop-valid'
|
||||
- '@types/react'
|
||||
- date-fns
|
||||
|
||||
'@date-fns/tz@1.5.0': {}
|
||||
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@emnapi/core@1.10.0':
|
||||
@@ -2776,6 +3103,23 @@ snapshots:
|
||||
'@eslint/core': 0.17.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/dom@1.7.6':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.5
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.6
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
'@floating-ui/utils@0.2.11': {}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
dependencies:
|
||||
'@humanfs/types': 0.15.0
|
||||
@@ -2959,6 +3303,11 @@ snapshots:
|
||||
|
||||
'@nolyfill/is-core-module@1.0.39': {}
|
||||
|
||||
'@phosphor-icons/react@2.1.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
@@ -2976,10 +3325,52 @@ snapshots:
|
||||
|
||||
'@rushstack/eslint-patch@1.16.1': {}
|
||||
|
||||
'@shikijs/core@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/primitive': 4.3.0
|
||||
'@shikijs/types': 4.3.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-html: 9.0.5
|
||||
|
||||
'@shikijs/engine-javascript@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.3.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
oniguruma-to-es: 4.3.6
|
||||
|
||||
'@shikijs/engine-oniguruma@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.3.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/langs@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.3.0
|
||||
|
||||
'@shikijs/primitive@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.3.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/themes@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 4.3.0
|
||||
|
||||
'@shikijs/types@4.3.0':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tabby_ai/hijri-converter@1.0.5': {}
|
||||
|
||||
'@tailwindcss/node@4.3.2':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
@@ -3056,10 +3447,18 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/json5@0.0.29': {}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@22.20.0':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -3072,6 +3471,8 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
@@ -3163,6 +3564,8 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.62.1
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@ungap/structured-clone@1.3.2': {}
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.12.2':
|
||||
optional: true
|
||||
|
||||
@@ -3373,11 +3776,17 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001800: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
character-entities-html4@2.1.0: {}
|
||||
|
||||
character-entities-legacy@3.0.0: {}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
@@ -3392,6 +3801,8 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -3422,6 +3833,10 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-data-view: 1.0.2
|
||||
|
||||
date-fns-jalali@4.1.0-0: {}
|
||||
|
||||
date-fns@4.4.0: {}
|
||||
|
||||
debug@3.2.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -3444,8 +3859,14 @@ snapshots:
|
||||
has-property-descriptors: 1.0.2
|
||||
object-keys: 1.1.1
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
devlop@1.1.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
doctrine@2.1.0:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
@@ -3914,6 +4335,15 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
framer-motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
motion-dom: 12.42.2
|
||||
motion-utils: 12.39.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -4004,6 +4434,26 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hast-util-to-html@9.0.5:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.3
|
||||
ccount: 2.0.1
|
||||
comma-separated-tokens: 2.0.3
|
||||
hast-util-whitespace: 3.0.0
|
||||
html-void-elements: 3.0.0
|
||||
mdast-util-to-hast: 13.2.1
|
||||
property-information: 7.2.0
|
||||
space-separated-tokens: 2.0.2
|
||||
stringify-entities: 4.0.4
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
@@ -4259,8 +4709,37 @@ snapshots:
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mdast-util-to-hast@13.2.1:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/mdast': 4.0.4
|
||||
'@ungap/structured-clone': 1.3.2
|
||||
devlop: 1.1.0
|
||||
micromark-util-sanitize-uri: 2.0.1
|
||||
trim-lines: 3.0.1
|
||||
unist-util-position: 5.0.0
|
||||
unist-util-visit: 5.1.0
|
||||
vfile: 6.0.3
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
micromark-util-character@2.1.1:
|
||||
dependencies:
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-util-encode@2.0.1: {}
|
||||
|
||||
micromark-util-sanitize-uri@2.0.1:
|
||||
dependencies:
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-encode: 2.0.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
|
||||
micromark-util-symbol@2.0.1: {}
|
||||
|
||||
micromark-util-types@2.0.2: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
@@ -4276,6 +4755,20 @@ snapshots:
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
motion-dom@12.42.2:
|
||||
dependencies:
|
||||
motion-utils: 12.39.0
|
||||
|
||||
motion-utils@12.39.0: {}
|
||||
|
||||
motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
framer-motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.15: {}
|
||||
@@ -4356,6 +4849,14 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
oniguruma-parser@0.12.2: {}
|
||||
|
||||
oniguruma-to-es@4.3.6:
|
||||
dependencies:
|
||||
oniguruma-parser: 0.12.2
|
||||
regex: 6.1.0
|
||||
regex-recursion: 6.0.2
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -4419,10 +4920,20 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
property-information@7.2.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
react-day-picker@9.14.0(react@19.2.7):
|
||||
dependencies:
|
||||
'@date-fns/tz': 1.5.0
|
||||
'@tabby_ai/hijri-converter': 1.0.5
|
||||
date-fns: 4.4.0
|
||||
date-fns-jalali: 4.1.0-0
|
||||
react: 19.2.7
|
||||
|
||||
react-dom@19.2.7(react@19.2.7):
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
@@ -4443,6 +4954,16 @@ snapshots:
|
||||
get-proto: 1.0.1
|
||||
which-builtin-type: 1.2.1
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
regex-utilities@2.3.0: {}
|
||||
|
||||
regex@6.1.0:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.9
|
||||
@@ -4452,6 +4973,8 @@ snapshots:
|
||||
gopd: 1.2.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
reselect@5.2.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
@@ -4556,6 +5079,17 @@ snapshots:
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
shiki@4.3.0:
|
||||
dependencies:
|
||||
'@shikijs/core': 4.3.0
|
||||
'@shikijs/engine-javascript': 4.3.0
|
||||
'@shikijs/engine-oniguruma': 4.3.0
|
||||
'@shikijs/langs': 4.3.0
|
||||
'@shikijs/themes': 4.3.0
|
||||
'@shikijs/types': 4.3.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
side-channel-list@1.0.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -4593,6 +5127,8 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
stable-hash@0.0.5: {}
|
||||
|
||||
stop-iteration-iterator@1.1.0:
|
||||
@@ -4651,6 +5187,11 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
stringify-entities@4.0.4:
|
||||
dependencies:
|
||||
character-entities-html4: 2.1.0
|
||||
character-entities-legacy: 3.0.0
|
||||
|
||||
strip-bom@3.0.0: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
@@ -4681,6 +5222,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
ts-api-utils@2.5.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
@@ -4759,6 +5302,29 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
unist-util-is@6.0.1:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
unist-util-position@5.0.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
unist-util-stringify-position@4.0.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
unist-util-visit-parents@6.0.2:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-is: 6.0.1
|
||||
|
||||
unist-util-visit@5.1.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-is: 6.0.1
|
||||
unist-util-visit-parents: 6.0.2
|
||||
|
||||
unrs-resolver@1.12.2:
|
||||
dependencies:
|
||||
napi-postinstall: 0.3.4
|
||||
@@ -4790,6 +5356,20 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
use-sync-external-store@1.6.0(react@19.2.7):
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
|
||||
vfile-message@4.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
unist-util-stringify-position: 4.0.0
|
||||
|
||||
vfile@6.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
dependencies:
|
||||
is-bigint: 1.1.0
|
||||
@@ -4838,3 +5418,5 @@ snapshots:
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
Reference in New Issue
Block a user