refactor: standardize card styles and improve layout across dashboard components
This commit is contained in:
@@ -32,14 +32,14 @@ export function AlertList({ alerts }: AlertListProps) {
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card className="rounded-md shadow-none">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t("dashboardHome.riskAlerts")}</CardTitle>
|
<CardTitle>{t("dashboardHome.riskAlerts")}</CardTitle>
|
||||||
<CardDescription>{t("dashboardHome.riskAlertsDescription")}</CardDescription>
|
<CardDescription>{t("dashboardHome.riskAlertsDescription")}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{alerts.length === 0 ? (
|
{alerts.length === 0 ? (
|
||||||
<div className="rounded-2xl border border-dashed px-4 py-10 text-center">
|
<div className="rounded-md border border-dashed px-4 py-10 text-center">
|
||||||
<ShieldAlertIcon className="mx-auto mb-3 size-8 text-muted-foreground" />
|
<ShieldAlertIcon className="mx-auto mb-3 size-8 text-muted-foreground" />
|
||||||
<div className="text-sm font-medium">{t("dashboardHome.noRiskTitle")}</div>
|
<div className="text-sm font-medium">{t("dashboardHome.noRiskTitle")}</div>
|
||||||
<div className="mt-1 text-sm text-muted-foreground">
|
<div className="mt-1 text-sm text-muted-foreground">
|
||||||
@@ -49,7 +49,7 @@ export function AlertList({ alerts }: AlertListProps) {
|
|||||||
) : (
|
) : (
|
||||||
alerts.map((item) => (
|
alerts.map((item) => (
|
||||||
<Link key={item.id} href={item.link} className="block">
|
<Link key={item.id} href={item.link} className="block">
|
||||||
<div className="rounded-2xl border p-4 transition-colors hover:border-primary/40">
|
<div className="rounded-md border p-4 transition-colors hover:border-primary/40">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ function LoadingCards() {
|
|||||||
return (
|
return (
|
||||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
||||||
{Array.from({ length: 6 }).map((_, index) => (
|
{Array.from({ length: 6 }).map((_, index) => (
|
||||||
<Card key={index}>
|
<Card key={index} className="rounded-md shadow-none">
|
||||||
<CardContent className="space-y-3 p-6">
|
<CardContent className="space-y-3 p-4">
|
||||||
<Skeleton className="h-4 w-24" />
|
<Skeleton className="h-4 w-24" />
|
||||||
<Skeleton className="h-8 w-20" />
|
<Skeleton className="h-8 w-20" />
|
||||||
<Skeleton className="h-4 w-full" />
|
<Skeleton className="h-4 w-full" />
|
||||||
@@ -72,7 +72,7 @@ export function DashboardHome() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col gap-6 p-4 lg:p-6">
|
<div className="flex flex-1 flex-col gap-4 p-4 lg:p-5">
|
||||||
<div className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
|
<div className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">{t("dashboardHome.title")}</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">{t("dashboardHome.title")}</h1>
|
||||||
@@ -82,7 +82,7 @@ export function DashboardHome() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<div className="rounded-xl border bg-muted/30 p-1">
|
<div className="rounded-md border bg-background p-1">
|
||||||
{rangeOptions.map((item) => (
|
{rangeOptions.map((item) => (
|
||||||
<Button
|
<Button
|
||||||
key={item.value}
|
key={item.value}
|
||||||
@@ -122,37 +122,37 @@ export function DashboardHome() {
|
|||||||
<div className="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]">
|
<div className="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]">
|
||||||
<TeamLoadPanel agentStats={data.agentStats} />
|
<TeamLoadPanel agentStats={data.agentStats} />
|
||||||
|
|
||||||
<Card>
|
<Card className="rounded-md shadow-none">
|
||||||
<CardContent className="grid gap-4 p-6 sm:grid-cols-2">
|
<CardContent className="grid gap-3 p-4 sm:grid-cols-2">
|
||||||
<div className="rounded-2xl border bg-muted/30 p-4">
|
<div className="rounded-md border bg-background px-3 py-2.5">
|
||||||
<div className="text-sm text-muted-foreground">{t("dashboardHome.enabledAiAgents")}</div>
|
<div className="text-sm text-muted-foreground">{t("dashboardHome.enabledAiAgents")}</div>
|
||||||
<div className="mt-2 text-3xl font-semibold">{data.aiStats.enabledAiAgents}</div>
|
<div className="mt-1 text-2xl font-semibold">{data.aiStats.enabledAiAgents}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border bg-muted/30 p-4">
|
<div className="rounded-md border bg-background px-3 py-2.5">
|
||||||
<div className="text-sm text-muted-foreground">{t("dashboardHome.enabledChannels")}</div>
|
<div className="text-sm text-muted-foreground">{t("dashboardHome.enabledChannels")}</div>
|
||||||
<div className="mt-2 text-3xl font-semibold">{data.aiStats.enabledChannels}</div>
|
<div className="mt-1 text-2xl font-semibold">{data.aiStats.enabledChannels}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border bg-muted/30 p-4">
|
<div className="rounded-md border bg-background px-3 py-2.5">
|
||||||
<div className="text-sm text-muted-foreground">{t("dashboardHome.todayKnowledgeRetrieves")}</div>
|
<div className="text-sm text-muted-foreground">{t("dashboardHome.todayKnowledgeRetrieves")}</div>
|
||||||
<div className="mt-2 text-3xl font-semibold">
|
<div className="mt-1 text-2xl font-semibold">
|
||||||
{data.aiStats.todayKnowledgeRetrieves}
|
{data.aiStats.todayKnowledgeRetrieves}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border bg-muted/30 p-4">
|
<div className="rounded-md border bg-background px-3 py-2.5">
|
||||||
<div className="text-sm text-muted-foreground">{t("dashboardHome.todayKnowledgeRetrieveFailRate")}</div>
|
<div className="text-sm text-muted-foreground">{t("dashboardHome.todayKnowledgeRetrieveFailRate")}</div>
|
||||||
<div className="mt-2 text-3xl font-semibold">
|
<div className="mt-1 text-2xl font-semibold">
|
||||||
{data.aiStats.todayKnowledgeRetrieveFailRate.toFixed(1)}%
|
{data.aiStats.todayKnowledgeRetrieveFailRate.toFixed(1)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border bg-muted/30 p-4">
|
<div className="rounded-md border bg-background px-3 py-2.5">
|
||||||
<div className="text-sm text-muted-foreground">{t("dashboardHome.todaySkillRunFailCount")}</div>
|
<div className="text-sm text-muted-foreground">{t("dashboardHome.todaySkillRunFailCount")}</div>
|
||||||
<div className="mt-2 text-3xl font-semibold">
|
<div className="mt-1 text-2xl font-semibold">
|
||||||
{data.aiStats.todaySkillRunFailCount}
|
{data.aiStats.todaySkillRunFailCount}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border bg-muted/30 p-4">
|
<div className="rounded-md border bg-background px-3 py-2.5">
|
||||||
<div className="text-sm text-muted-foreground">{t("dashboardHome.todayAiHandoffCount")}</div>
|
<div className="text-sm text-muted-foreground">{t("dashboardHome.todayAiHandoffCount")}</div>
|
||||||
<div className="mt-2 text-3xl font-semibold">
|
<div className="mt-1 text-2xl font-semibold">
|
||||||
{data.aiStats.todayAiHandoffCount}
|
{data.aiStats.todayAiHandoffCount}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +163,7 @@ export function DashboardHome() {
|
|||||||
<AlertList alerts={data.alerts} />
|
<AlertList alerts={data.alerts} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Card>
|
<Card className="rounded-md shadow-none">
|
||||||
<CardContent className="flex min-h-60 items-center justify-center p-6 text-sm text-muted-foreground">
|
<CardContent className="flex min-h-60 items-center justify-center p-6 text-sm text-muted-foreground">
|
||||||
{t("dashboardHome.empty")}
|
{t("dashboardHome.empty")}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function SummaryCards({ summary }: SummaryCardsProps) {
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-5">
|
||||||
{cards.map((item) => {
|
{cards.map((item) => {
|
||||||
const Icon = item.icon
|
const Icon = item.icon
|
||||||
const rawValue = summary[item.key]
|
const rawValue = summary[item.key]
|
||||||
@@ -86,18 +86,18 @@ export function SummaryCards({ summary }: SummaryCardsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link key={item.key} href={item.link}>
|
<Link key={item.key} href={item.link}>
|
||||||
<Card className="h-full transition-colors hover:border-primary/40">
|
<Card className="h-full rounded-md shadow-none transition-colors hover:border-primary/40">
|
||||||
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3">
|
<CardHeader className="flex flex-row items-start justify-between space-y-0 p-4 pb-2">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<CardTitle className="text-sm font-medium">{t(item.titleKey)}</CardTitle>
|
<CardTitle className="text-sm font-medium">{t(item.titleKey)}</CardTitle>
|
||||||
<CardDescription>{t(item.descriptionKey)}</CardDescription>
|
<CardDescription>{t(item.descriptionKey)}</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full bg-primary/10 p-2 text-primary">
|
<div className="rounded-md bg-muted p-2 text-muted-foreground">
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="px-4 pb-4">
|
||||||
<div className="text-3xl font-semibold tracking-tight">{value}</div>
|
<div className="text-2xl font-semibold tracking-tight">{value}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function TeamLoadPanel({ agentStats }: TeamLoadPanelProps) {
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card className="rounded-md shadow-none">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t("dashboardHome.teamLoad")}</CardTitle>
|
<CardTitle>{t("dashboardHome.teamLoad")}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -42,12 +42,12 @@ export function TeamLoadPanel({ agentStats }: TeamLoadPanelProps) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{agentStats.teamLoads.length === 0 ? (
|
{agentStats.teamLoads.length === 0 ? (
|
||||||
<div className="rounded-xl border border-dashed px-4 py-8 text-center text-sm text-muted-foreground">
|
<div className="rounded-md border border-dashed px-4 py-8 text-center text-sm text-muted-foreground">
|
||||||
{t("dashboardHome.noTeamData")}
|
{t("dashboardHome.noTeamData")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
agentStats.teamLoads.map((item) => (
|
agentStats.teamLoads.map((item) => (
|
||||||
<div key={item.teamId} className="rounded-2xl border p-4">
|
<div key={item.teamId} className="rounded-md border p-4">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -86,19 +86,19 @@ export function TeamLoadPanel({ agentStats }: TeamLoadPanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 grid gap-3 text-sm md:grid-cols-2 xl:grid-cols-4">
|
<div className="mt-4 grid gap-3 text-sm md:grid-cols-2 xl:grid-cols-4">
|
||||||
<div className="rounded-xl bg-muted/40 px-3 py-2">
|
<div className="rounded-md bg-muted/35 px-3 py-2">
|
||||||
<div className="text-muted-foreground">{t("dashboardHome.waiting")}</div>
|
<div className="text-muted-foreground">{t("dashboardHome.waiting")}</div>
|
||||||
<div className="mt-1 text-lg font-semibold">{item.waitingConversations}</div>
|
<div className="mt-1 text-lg font-semibold">{item.waitingConversations}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl bg-muted/40 px-3 py-2">
|
<div className="rounded-md bg-muted/35 px-3 py-2">
|
||||||
<div className="text-muted-foreground">{t("dashboardHome.processing")}</div>
|
<div className="text-muted-foreground">{t("dashboardHome.processing")}</div>
|
||||||
<div className="mt-1 text-lg font-semibold">{item.processingConversations}</div>
|
<div className="mt-1 text-lg font-semibold">{item.processingConversations}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl bg-muted/40 px-3 py-2">
|
<div className="rounded-md bg-muted/35 px-3 py-2">
|
||||||
<div className="text-muted-foreground">{t("dashboardHome.capacity")}</div>
|
<div className="text-muted-foreground">{t("dashboardHome.capacity")}</div>
|
||||||
<div className="mt-1 text-lg font-semibold">{item.maxConcurrentCapacity}</div>
|
<div className="mt-1 text-lg font-semibold">{item.maxConcurrentCapacity}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl bg-muted/40 px-3 py-2">
|
<div className="rounded-md bg-muted/35 px-3 py-2">
|
||||||
<div className="text-muted-foreground">{t("dashboardHome.busyAgents")}</div>
|
<div className="text-muted-foreground">{t("dashboardHome.busyAgents")}</div>
|
||||||
<div className="mt-1 text-lg font-semibold">{item.busyAgents}</div>
|
<div className="mt-1 text-lg font-semibold">{item.busyAgents}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function TrendPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 xl:grid-cols-[1.5fr_0.9fr]">
|
<div className="grid gap-4 xl:grid-cols-[1.5fr_0.9fr]">
|
||||||
<Card>
|
<Card className="rounded-md shadow-none">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{title}</CardTitle>
|
<CardTitle>{title}</CardTitle>
|
||||||
<CardDescription>{description}</CardDescription>
|
<CardDescription>{description}</CardDescription>
|
||||||
@@ -90,7 +90,7 @@ export function TrendPanel({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card className="rounded-md shadow-none">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t("dashboardHome.statusDistribution")}</CardTitle>
|
<CardTitle>{t("dashboardHome.statusDistribution")}</CardTitle>
|
||||||
<CardDescription>{t("dashboardHome.statusDistributionDescription")}</CardDescription>
|
<CardDescription>{t("dashboardHome.statusDistributionDescription")}</CardDescription>
|
||||||
|
|||||||
@@ -844,7 +844,7 @@ function EditDialogBody({
|
|||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-lg border bg-muted/10 p-4">
|
<div className="rounded-md border bg-muted/10 p-3">
|
||||||
<div className="mb-1 text-sm font-medium">{t("aiAgent.teams")}</div>
|
<div className="mb-1 text-sm font-medium">{t("aiAgent.teams")}</div>
|
||||||
<div className="mb-4 text-xs text-muted-foreground">
|
<div className="mb-4 text-xs text-muted-foreground">
|
||||||
{t("aiAgent.currentHandoffMode", { mode: selectedHandoffModeLabel })}
|
{t("aiAgent.currentHandoffMode", { mode: selectedHandoffModeLabel })}
|
||||||
@@ -1270,7 +1270,7 @@ function SectionCard({
|
|||||||
const [open, setOpen] = useState(defaultOpen);
|
const [open, setOpen] = useState(defaultOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="rounded-lg border bg-card p-5">
|
<section className="border-t pt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex w-full items-start justify-between gap-4 text-left"
|
className="flex w-full items-start justify-between gap-4 text-left"
|
||||||
@@ -1285,7 +1285,7 @@ function SectionCard({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<span className="mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-full border bg-muted/30 text-muted-foreground transition-colors hover:bg-muted/50">
|
<span className="mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md border bg-muted/30 text-muted-foreground transition-colors hover:bg-muted/50">
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={`size-4 transition-transform duration-200 ${
|
className={`size-4 transition-transform duration-200 ${
|
||||||
open ? "rotate-180" : "rotate-0"
|
open ? "rotate-180" : "rotate-0"
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default function DashboardChannelsPage() {
|
|||||||
label: t("channel.columnChannel"),
|
label: t("channel.columnChannel"),
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-2xl bg-muted">
|
<div className="flex size-10 items-center justify-center rounded-md bg-muted">
|
||||||
<ChannelIcon channelType={item.channelType} />
|
<ChannelIcon channelType={item.channelType} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ export default function DashboardLayout({
|
|||||||
|
|
||||||
if (!ready || !session) {
|
if (!ready || !session) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-[linear-gradient(160deg,#f3f1e8_0%,#f8faf5_46%,#e8f7f2_100%)] p-6">
|
<div className="flex min-h-screen items-center justify-center bg-background p-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-full bg-primary/10 text-primary">
|
<div className="flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
||||||
<Loader2Icon className="size-5 animate-spin" />
|
<Loader2Icon className="size-5 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -63,9 +63,9 @@ export default function DashboardLayout({
|
|||||||
>
|
>
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<AppSidebar variant="inset" />
|
<AppSidebar variant="inset" />
|
||||||
<SidebarInset className="overflow-hidden border border-border/65 shadow-[0_18px_50px_rgba(20,83,69,0.09)] dark:shadow-none">
|
<SidebarInset className="overflow-hidden border-l border-border/70 bg-background">
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<div className="@container/main flex min-h-0 flex-1 flex-col gap-2 overflow-auto">
|
<div className="@container/main flex min-h-0 flex-1 flex-col overflow-auto">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function DashboardPermissionsPage() {
|
|||||||
label: t("permission.columnPermission"),
|
label: t("permission.columnPermission"),
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
|
<div className="flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
||||||
<KeyRoundIcon className="size-4" />
|
<KeyRoundIcon className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ function AssignPermissionsDrawerBody({
|
|||||||
const value = field.value || [];
|
const value = field.value || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 min-h-0 space-y-4 overflow-y-auto rounded-xl border p-3">
|
<div className="flex-1 min-h-0 space-y-4 overflow-y-auto rounded-md border p-3">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="py-8 text-center text-sm text-muted-foreground">
|
<div className="py-8 text-center text-sm text-muted-foreground">
|
||||||
{t("role.loadingPermissions")}
|
{t("role.loadingPermissions")}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ function SortableRoleRow({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
|
<div className="flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
||||||
<ShieldCheckIcon className="size-4" />
|
<ShieldCheckIcon className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="font-medium">{displayName}</div>
|
<div className="font-medium">{displayName}</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function InitialPasswordDialog({
|
|||||||
{t("user.initialPasswordDescription", { username: username || "-" })}
|
{t("user.initialPasswordDescription", { username: username || "-" })}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="rounded-xl border bg-muted/40 p-4">
|
<div className="rounded-md border bg-muted/35 p-4">
|
||||||
<div className="text-xs text-muted-foreground">{t("user.initialPassword")}</div>
|
<div className="text-xs text-muted-foreground">{t("user.initialPassword")}</div>
|
||||||
<div className="mt-2 break-all font-mono text-base">{password}</div>
|
<div className="mt-2 break-all font-mono text-base">{password}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function ResetPasswordDialogs({
|
|||||||
{t("user.resetSuccessDescription", { username: item?.username || "-" })}
|
{t("user.resetSuccessDescription", { username: item?.username || "-" })}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="rounded-xl border bg-muted/40 p-4">
|
<div className="rounded-md border bg-muted/35 p-4">
|
||||||
<div className="text-xs text-muted-foreground">{t("user.newPassword")}</div>
|
<div className="text-xs text-muted-foreground">{t("user.newPassword")}</div>
|
||||||
<div className="mt-2 break-all font-mono text-base">{password}</div>
|
<div className="mt-2 break-all font-mono text-base">{password}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ export default function DashboardUsersPage() {
|
|||||||
<TableRow key={item.id}>
|
<TableRow key={item.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
|
<div className="flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
||||||
<UserRoundIcon className="size-4" />
|
<UserRoundIcon className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import type { ReactNode } from "react"
|
import type { ReactNode } from "react"
|
||||||
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { TableCell, TableRow } from "@/components/ui/table"
|
import { TableCell, TableRow } from "@/components/ui/table"
|
||||||
import { useI18n } from "@/i18n/provider"
|
import { useI18n } from "@/i18n/provider"
|
||||||
|
import { getPageTitleKey } from "@/lib/navigation"
|
||||||
|
|
||||||
export function DashboardPage({
|
export function DashboardPage({
|
||||||
className,
|
className,
|
||||||
@@ -13,8 +15,15 @@ export function DashboardPage({
|
|||||||
className?: string
|
className?: string
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
|
const t = useI18n()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const title = t(getPageTitleKey(pathname))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-1 flex-col gap-5 p-4 lg:p-6", className)}>
|
<div className={cn("flex flex-1 flex-col gap-4 p-4 lg:p-5", className)}>
|
||||||
|
<div className="flex min-h-8 items-center">
|
||||||
|
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
||||||
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -32,7 +41,7 @@ export function DashboardToolbar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-3 rounded-lg border border-border/70 bg-card/95 p-3 shadow-[0_1px_2px_rgba(15,23,42,0.04)] lg:flex-row lg:items-center lg:justify-between dark:shadow-none",
|
"flex flex-col gap-3 border-b border-border/70 bg-background pb-3 lg:flex-row lg:items-center lg:justify-between",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -56,8 +65,8 @@ export function DashboardTableShell({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("space-y-4", className)}>
|
<div className={cn("space-y-3", className)}>
|
||||||
<div className="overflow-hidden rounded-lg border border-border/70 bg-card text-card-foreground shadow-[0_1px_2px_rgba(15,23,42,0.035)] dark:shadow-none">
|
<div className="overflow-hidden rounded-md border border-border/70 bg-card text-card-foreground">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
{pagination ? <div>{pagination}</div> : null}
|
{pagination ? <div>{pagination}</div> : null}
|
||||||
|
|||||||
@@ -28,25 +28,25 @@ export function DashboardPlaceholder({
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col gap-6 p-4 pt-4 lg:p-6 lg:pt-6">
|
<div className="flex flex-1 flex-col gap-4 p-4 lg:p-5">
|
||||||
<Card className="border-dashed">
|
<Card className="rounded-md border-dashed shadow-none">
|
||||||
<CardHeader className="gap-3">
|
<CardHeader className="gap-2 p-4">
|
||||||
<span className="text-xs font-medium tracking-[0.24em] uppercase text-muted-foreground">
|
<span className="text-xs font-medium uppercase text-muted-foreground">
|
||||||
{eyebrow}
|
{eyebrow}
|
||||||
</span>
|
</span>
|
||||||
<CardTitle className="text-3xl">{title}</CardTitle>
|
<CardTitle className="text-xl">{title}</CardTitle>
|
||||||
<CardDescription className="max-w-2xl text-sm leading-6">
|
<CardDescription className="max-w-2xl text-sm leading-6">
|
||||||
{description}
|
{description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-4 md:grid-cols-[1.2fr_0.8fr]">
|
<CardContent className="grid gap-3 p-4 pt-0 md:grid-cols-[1.2fr_0.8fr]">
|
||||||
<div className="rounded-2xl border bg-muted/40 p-5">
|
<div className="rounded-md border bg-muted/30 p-4">
|
||||||
<p className="text-sm font-medium">{t("placeholder.nextSteps")}</p>
|
<p className="text-sm font-medium">{t("placeholder.nextSteps")}</p>
|
||||||
<div className="mt-4 grid gap-3">
|
<div className="mt-3 grid gap-2">
|
||||||
{nextSteps.map((item) => (
|
{nextSteps.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item}
|
key={item}
|
||||||
className="flex items-start gap-3 rounded-xl bg-background p-3"
|
className="flex items-start gap-3 rounded-md bg-background p-3"
|
||||||
>
|
>
|
||||||
<Clock3Icon className="mt-0.5 size-4 text-muted-foreground" />
|
<Clock3Icon className="mt-0.5 size-4 text-muted-foreground" />
|
||||||
<p className="text-sm">{item}</p>
|
<p className="text-sm">{item}</p>
|
||||||
@@ -54,14 +54,14 @@ export function DashboardPlaceholder({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-between rounded-2xl border bg-background p-5">
|
<div className="flex flex-col justify-between rounded-md border bg-background p-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">{t("placeholder.status")}</p>
|
<p className="text-sm font-medium">{t("placeholder.status")}</p>
|
||||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
||||||
{t("placeholder.statusDescription")}
|
{t("placeholder.statusDescription")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" className="mt-6 justify-between">
|
<Button variant="outline" className="mt-5 justify-between">
|
||||||
{t("placeholder.nextPhase")}
|
{t("placeholder.nextPhase")}
|
||||||
<ArrowUpRightIcon />
|
<ArrowUpRightIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { useState } from "react"
|
|||||||
import { useAuth } from "@/components/auth-provider"
|
import { useAuth } from "@/components/auth-provider"
|
||||||
import { useI18n } from "@/i18n/provider"
|
import { useI18n } from "@/i18n/provider"
|
||||||
import { ChangePasswordDialog } from "@/components/change-password-dialog"
|
import { ChangePasswordDialog } from "@/components/change-password-dialog"
|
||||||
|
import { LocaleSwitcher } from "@/components/locale-switcher"
|
||||||
import { useNotifications } from "@/components/notification-provider"
|
import { useNotifications } from "@/components/notification-provider"
|
||||||
|
import { PaletteToggle } from "@/components/palette-toggle"
|
||||||
|
import { ThemeToggle } from "@/components/theme-toggle"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -96,6 +99,17 @@ export function NavUser({
|
|||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel className="px-2 py-1.5 text-xs font-normal text-muted-foreground">
|
||||||
|
{t("nav.preferences")}
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<div className="flex items-center gap-2 px-2 pb-2">
|
||||||
|
<LocaleSwitcher />
|
||||||
|
<PaletteToggle />
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -3,13 +3,10 @@
|
|||||||
import { useEffect, useRef } from "react"
|
import { useEffect, useRef } from "react"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
|
|
||||||
import { LocaleSwitcher } from "@/components/locale-switcher"
|
|
||||||
import { RealtimeConnectionStatus } from "@/components/realtime-connection-status"
|
import { RealtimeConnectionStatus } from "@/components/realtime-connection-status"
|
||||||
import { useI18n } from "@/i18n/provider"
|
import { useI18n } from "@/i18n/provider"
|
||||||
import { getPageTitleKey } from "@/lib/navigation"
|
import { getPageTitleKey } from "@/lib/navigation"
|
||||||
import { useAgentConversationsStore } from "@/lib/stores/agent-conversations"
|
import { useAgentConversationsStore } from "@/lib/stores/agent-conversations"
|
||||||
import { PaletteToggle } from "@/components/palette-toggle"
|
|
||||||
import { ThemeToggle } from "@/components/theme-toggle"
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
@@ -77,11 +74,6 @@ export function SiteHeader() {
|
|||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2">
|
|
||||||
<LocaleSwitcher />
|
|
||||||
<PaletteToggle />
|
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2343,6 +2343,7 @@
|
|||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"dashboardHome": "Dashboard",
|
"dashboardHome": "Dashboard",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
"preferences": "Preferences",
|
||||||
"changePassword": "Change Password",
|
"changePassword": "Change Password",
|
||||||
"signOut": "Sign Out"
|
"signOut": "Sign Out"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2343,6 +2343,7 @@
|
|||||||
"permissions": "权限管理",
|
"permissions": "权限管理",
|
||||||
"dashboardHome": "后台总览",
|
"dashboardHome": "后台总览",
|
||||||
"notifications": "通知中心",
|
"notifications": "通知中心",
|
||||||
|
"preferences": "偏好设置",
|
||||||
"changePassword": "修改密码",
|
"changePassword": "修改密码",
|
||||||
"signOut": "退出登录"
|
"signOut": "退出登录"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user