From 6d2803f41f3db753da138c2d66dd1a44375a8a81 Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Mon, 16 Mar 2026 17:41:03 +0800 Subject: [PATCH] fix: add my skills preview to dashboard --- web/src/i18n/locales/en.json | 9 +- web/src/i18n/locales/zh.json | 9 +- web/src/pages/dashboard-preview.test.ts | 20 +++++ web/src/pages/dashboard-preview.ts | 14 ++++ web/src/pages/dashboard.tsx | 104 +++++++++++++++++++++++- 5 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 web/src/pages/dashboard-preview.test.ts create mode 100644 web/src/pages/dashboard-preview.ts diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 150a292c..738bd059 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -223,13 +223,20 @@ "platformRoles": "Platform Roles", "starsAndRatings": "Stars & Ratings", "viewStars": "View My Stars", + "mySkillsTitle": "My Skills", + "openMySkills": "Open my skills", + "mySkillsPreviewDescription": "Only the first few items are shown here. Open My Skills to see the rest.", + "mySkillsPreviewEmpty": "You have not published any skills yet", "credentials": "Credentials", "openTokens": "Open Token Page", + "tokensPreviewDescription": "Only the first few tokens are shown here. Open the token page to see the rest.", + "tokensPreviewEmpty": "No API tokens yet", "governanceTitle": "Review & Governance", "viewGovernance": "Open governance center", "viewPromotions": "View Promotions", "reportsTitle": "Report Management", - "viewReports": "View skill reports" + "viewReports": "View skill reports", + "previewMore": "..." }, "mySkills": { "title": "My Skills", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 6602a769..ac5dd007 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -223,13 +223,20 @@ "platformRoles": "平台角色", "starsAndRatings": "收藏与评分", "viewStars": "查看我的收藏", + "mySkillsTitle": "我的技能", + "openMySkills": "打开我的技能", + "mySkillsPreviewDescription": "这里只展示最近的几条,更多内容请进入我的技能页查看。", + "mySkillsPreviewEmpty": "你还没有发布任何技能", "credentials": "访问凭证", "openTokens": "打开 Token 页面", + "tokensPreviewDescription": "这里只展示前几条 Token,更多内容请进入 Token 页面查看。", + "tokensPreviewEmpty": "你还没有创建任何 Token", "governanceTitle": "审核与治理", "viewGovernance": "打开治理中心", "viewPromotions": "查看提升审核", "reportsTitle": "举报管理", - "viewReports": "查看技能举报" + "viewReports": "查看技能举报", + "previewMore": "..." }, "mySkills": { "title": "我的技能", diff --git a/web/src/pages/dashboard-preview.test.ts b/web/src/pages/dashboard-preview.test.ts new file mode 100644 index 00000000..368d8e43 --- /dev/null +++ b/web/src/pages/dashboard-preview.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { limitPreviewItems } from './dashboard-preview' + +describe('limitPreviewItems', () => { + it('returns all items when the list is within the limit', () => { + expect(limitPreviewItems(['a', 'b'], 3)).toEqual({ + items: ['a', 'b'], + hasMore: false, + remainingCount: 0, + }) + }) + + it('returns only the first items and reports the remaining count', () => { + expect(limitPreviewItems(['a', 'b', 'c', 'd'], 3)).toEqual({ + items: ['a', 'b', 'c'], + hasMore: true, + remainingCount: 1, + }) + }) +}) diff --git a/web/src/pages/dashboard-preview.ts b/web/src/pages/dashboard-preview.ts new file mode 100644 index 00000000..66fd18c9 --- /dev/null +++ b/web/src/pages/dashboard-preview.ts @@ -0,0 +1,14 @@ +export function limitPreviewItems(items: T[], limit: number): { + items: T[] + hasMore: boolean + remainingCount: number +} { + const visibleItems = items.slice(0, limit) + const remainingCount = Math.max(items.length - visibleItems.length, 0) + + return { + items: visibleItems, + hasMore: remainingCount > 0, + remainingCount, + } +} diff --git a/web/src/pages/dashboard.tsx b/web/src/pages/dashboard.tsx index 12d96d56..63b652a5 100644 --- a/web/src/pages/dashboard.tsx +++ b/web/src/pages/dashboard.tsx @@ -1,13 +1,26 @@ +import { useQuery } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' +import { tokenApi } from '@/api/client' import { useAuth } from '@/features/auth/use-auth' +import { useMySkills } from '@/shared/hooks/use-skill-queries' import { TokenList } from '@/features/token/token-list' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/ui/card' +import { limitPreviewItems } from './dashboard-preview' + +const DASHBOARD_PREVIEW_LIMIT = 3 export function DashboardPage() { const { t } = useTranslation() const { user, hasRole } = useAuth() const governanceVisible = hasRole('SKILL_ADMIN') || hasRole('SUPER_ADMIN') + const { data: skills, isLoading: isLoadingSkills } = useMySkills() + const { data: tokenPage, isLoading: isLoadingTokens } = useQuery({ + queryKey: ['tokens', 'dashboard-preview'], + queryFn: () => tokenApi.getTokens({ page: 0, size: DASHBOARD_PREVIEW_LIMIT + 1 }), + }) + const skillPreview = limitPreviewItems(skills ?? [], DASHBOARD_PREVIEW_LIMIT) + const tokenPreview = limitPreviewItems(tokenPage?.items ?? [], DASHBOARD_PREVIEW_LIMIT) return (
@@ -59,13 +72,19 @@ export function DashboardPage() { -
+
{t('dashboard.starsAndRatings')}
{t('dashboard.viewStars')}
+ +
{t('dashboard.mySkillsTitle')}
+ + {t('dashboard.openMySkills')} + +
{t('dashboard.credentials')}
@@ -88,6 +107,89 @@ export function DashboardPage() { ) : null}
+
+ + + {t('mySkills.title')} + {t('dashboard.mySkillsPreviewDescription')} + + + {isLoadingSkills ? ( +
+ {Array.from({ length: DASHBOARD_PREVIEW_LIMIT }).map((_, index) => ( +
+ ))} +
+ ) : skillPreview.items.length > 0 ? ( + <> + {skillPreview.items.map((skill) => ( + +
+
{skill.displayName}
+
@{skill.namespace}
+
+ {skill.latestVersion ? ( + + v{skill.latestVersion} + + ) : null} + + ))} + {skillPreview.hasMore ? ( +
+ {t('dashboard.previewMore', { count: skillPreview.remainingCount })} +
+ ) : null} + + ) : ( +
{t('dashboard.mySkillsPreviewEmpty')}
+ )} + + + + + + {t('token.title')} + {t('dashboard.tokensPreviewDescription')} + + + {isLoadingTokens ? ( +
+ {Array.from({ length: DASHBOARD_PREVIEW_LIMIT }).map((_, index) => ( +
+ ))} +
+ ) : tokenPreview.items.length > 0 ? ( + <> + {tokenPreview.items.map((token) => ( +
+
+
{token.name}
+
{token.tokenPrefix}...
+
+
+ ))} + {tokenPreview.hasMore ? ( +
+ {t('dashboard.previewMore', { count: tokenPreview.remainingCount })} +
+ ) : null} + + ) : ( +
{t('dashboard.tokensPreviewEmpty')}
+ )} + + +
+
)