Add workspace landing page
This commit is contained in:
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
||||
const requiredFiles = [
|
||||
'src/app/App.tsx',
|
||||
'src/pages/login.tsx',
|
||||
'src/pages/workspace.tsx',
|
||||
'src/pages/project.tsx',
|
||||
'src/pages/projects/project-overview.tsx',
|
||||
'src/pages/projects/project-data.tsx',
|
||||
|
||||
@@ -34,10 +34,13 @@ const metrics = await page.evaluate(() => {
|
||||
const statusbar = document.querySelector('.statusbar')
|
||||
const search = document.querySelector('.global-search')
|
||||
const main = document.querySelector('.workbench-main')
|
||||
const workspacePage = document.querySelector('.workspace-page')
|
||||
const rail = document.querySelector('.project-rail')
|
||||
const railChildren = rail?.querySelector('.arco-layout-sider-children')
|
||||
const sidebar = document.querySelector('.channel-sidebar')
|
||||
const sidebarTitle = document.querySelector('.project-title h5')
|
||||
const stage = document.querySelector('.stage')
|
||||
const inspectorTitle = document.querySelector('.inspector-title h5')
|
||||
const inspector = document.querySelector('.inspector')
|
||||
const dashboard = document.querySelector('.dashboard-button')
|
||||
const project = document.querySelector('.project-button')
|
||||
@@ -72,13 +75,18 @@ const metrics = await page.evaluate(() => {
|
||||
statusbarHeight: statusbar?.getBoundingClientRect().height,
|
||||
searchHeight: search?.getBoundingClientRect().height,
|
||||
workbenchMain: rect(main),
|
||||
hasWorkspacePage: Boolean(workspacePage),
|
||||
projectRailWidth: rail?.getBoundingClientRect().width,
|
||||
projectRail: rect(rail),
|
||||
channelSidebar: rect(sidebar),
|
||||
sidebarTitleText: sidebarTitle?.textContent ?? '',
|
||||
stage: rect(stage),
|
||||
inspector: rect(inspector),
|
||||
inspectorTitleText: inspectorTitle?.textContent ?? '',
|
||||
dashboardButton: rect(dashboard),
|
||||
dashboardButtonActive: dashboard?.classList.contains('active') ?? false,
|
||||
projectButton: rect(project),
|
||||
projectButtonActive: project?.classList.contains('active') ?? false,
|
||||
createProjectButton: rect(create),
|
||||
firstProjectBadge: rect(firstBadge),
|
||||
horizontalInsets,
|
||||
@@ -101,6 +109,15 @@ if (metrics.statusbarHeight !== 32) failures.push(`expected statusbar height 32,
|
||||
if (metrics.searchHeight !== 42) failures.push(`expected search height 42, got ${metrics.searchHeight}`)
|
||||
if (metrics.overflowX) failures.push('expected no horizontal overflow')
|
||||
if (!metrics.workbenchMain) failures.push('missing workbench main')
|
||||
if (!metrics.hasWorkspacePage) failures.push('expected login to land on workspace page')
|
||||
if (!metrics.dashboardButtonActive) failures.push('expected dashboard button to be active after login')
|
||||
if (metrics.projectButtonActive) failures.push('expected first project button not to be active on workspace landing page')
|
||||
if (!metrics.sidebarTitleText.includes('工作台')) {
|
||||
failures.push(`expected sidebar title to describe workspace landing page, got ${JSON.stringify(metrics.sidebarTitleText)}`)
|
||||
}
|
||||
if (!metrics.inspectorTitleText.includes('工作台')) {
|
||||
failures.push(`expected inspector title to describe workspace landing page, got ${JSON.stringify(metrics.inspectorTitleText)}`)
|
||||
}
|
||||
if (!metrics.projectRail || !metrics.channelSidebar || !metrics.stage || !metrics.inspector) {
|
||||
failures.push(
|
||||
`missing main layout regions: rail=${JSON.stringify(metrics.projectRail)}, sidebar=${JSON.stringify(metrics.channelSidebar)}, stage=${JSON.stringify(metrics.stage)}, inspector=${JSON.stringify(metrics.inspector)}`,
|
||||
|
||||
@@ -312,6 +312,12 @@
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dashboard-button.active {
|
||||
border-color: var(--senlin-primary);
|
||||
background: var(--senlin-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.create-project {
|
||||
font-size: 11px;
|
||||
white-space: normal;
|
||||
|
||||
@@ -5,11 +5,12 @@ import '../App.css'
|
||||
import { LoginPage } from '../pages/login'
|
||||
import { ProjectPage } from '../pages/project'
|
||||
import { projects } from '../pages/projects/project-data'
|
||||
import type { ChannelKey, Screen, Theme } from '../pages/projects/project-types'
|
||||
import type { ChannelKey, Screen, Theme, WorkbenchView } from '../pages/projects/project-types'
|
||||
|
||||
function App() {
|
||||
const [screen, setScreen] = useState<Screen>('login')
|
||||
const [theme, setTheme] = useState<Theme>('light')
|
||||
const [activeView, setActiveView] = useState<WorkbenchView>('workspace')
|
||||
const [activeProject, setActiveProject] = useState(projects[0])
|
||||
const [activeChannel, setActiveChannel] = useState<ChannelKey>('overview')
|
||||
const [selectedItem, setSelectedItem] = useState('Inbox 待处理(36)')
|
||||
@@ -23,11 +24,16 @@ function App() {
|
||||
<LoginPage onLogin={() => setScreen('workbench')} />
|
||||
) : (
|
||||
<ProjectPage
|
||||
activeView={activeView}
|
||||
activeProject={activeProject}
|
||||
activeChannel={activeChannel}
|
||||
selectedItem={selectedItem}
|
||||
theme={theme}
|
||||
onSelectProject={setActiveProject}
|
||||
onSelectWorkspace={() => setActiveView('workspace')}
|
||||
onSelectProject={(project) => {
|
||||
setActiveProject(project)
|
||||
setActiveView('project')
|
||||
}}
|
||||
onSelectChannel={setActiveChannel}
|
||||
onSelectItem={setSelectedItem}
|
||||
onToggleTheme={() => setTheme(dark ? 'light' : 'dark')}
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import { Layout } from '@arco-design/web-react'
|
||||
import { WorkspacePage } from './workspace'
|
||||
import { ProjectOverview } from './projects/project-overview'
|
||||
import { ProjectInspector } from './projects/project-inspector'
|
||||
import { ProjectRail } from './projects/project-rail'
|
||||
import { ProjectSidebar } from './projects/project-sidebar'
|
||||
import { ProjectStatusbar } from './projects/project-statusbar'
|
||||
import { ProjectTopbar } from './projects/project-topbar'
|
||||
import type { ChannelKey, Project, Theme } from './projects/project-types'
|
||||
import type { ChannelKey, Project, Theme, WorkbenchView } from './projects/project-types'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export function ProjectPage({
|
||||
activeView,
|
||||
activeProject,
|
||||
activeChannel,
|
||||
selectedItem,
|
||||
theme,
|
||||
onSelectProject,
|
||||
onSelectWorkspace,
|
||||
onSelectChannel,
|
||||
onSelectItem,
|
||||
onToggleTheme,
|
||||
}: {
|
||||
activeView: WorkbenchView
|
||||
activeProject: Project
|
||||
activeChannel: ChannelKey
|
||||
selectedItem: string
|
||||
theme: Theme
|
||||
onSelectProject: (project: Project) => void
|
||||
onSelectWorkspace: () => void
|
||||
onSelectChannel: (key: ChannelKey) => void
|
||||
onSelectItem: (title: string) => void
|
||||
onToggleTheme: () => void
|
||||
@@ -33,9 +38,15 @@ export function ProjectPage({
|
||||
<ProjectTopbar theme={theme} onToggleTheme={onToggleTheme} />
|
||||
|
||||
<Layout className="workbench-main" hasSider>
|
||||
<ProjectRail activeProject={activeProject} onSelectProject={onSelectProject} />
|
||||
<ProjectRail
|
||||
activeProject={activeProject}
|
||||
activeView={activeView}
|
||||
onSelectWorkspace={onSelectWorkspace}
|
||||
onSelectProject={onSelectProject}
|
||||
/>
|
||||
|
||||
<ProjectSidebar
|
||||
activeView={activeView}
|
||||
activeProject={activeProject}
|
||||
activeChannel={activeChannel}
|
||||
onSelectChannel={onSelectChannel}
|
||||
@@ -43,10 +54,19 @@ export function ProjectPage({
|
||||
/>
|
||||
|
||||
<Content className="stage">
|
||||
<ProjectOverview onSelectItem={onSelectItem} />
|
||||
{activeView === 'workspace' ? (
|
||||
<WorkspacePage onSelectProject={onSelectProject} />
|
||||
) : (
|
||||
<ProjectOverview onSelectItem={onSelectItem} />
|
||||
)}
|
||||
</Content>
|
||||
|
||||
<ProjectInspector activeProject={activeProject} activeChannel={activeChannel} selectedItem={selectedItem} />
|
||||
<ProjectInspector
|
||||
activeView={activeView}
|
||||
activeProject={activeProject}
|
||||
activeChannel={activeChannel}
|
||||
selectedItem={activeView === 'workspace' ? '工作台动态' : selectedItem}
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
<ProjectStatusbar />
|
||||
|
||||
@@ -1,18 +1,45 @@
|
||||
import { Avatar, Button, Divider, Input, Layout, Message, Space, Tabs, Typography } from '@arco-design/web-react'
|
||||
import { IconLink, IconMessage, IconMore } from '@arco-design/web-react/icon'
|
||||
import { channels, discussions } from './project-data'
|
||||
import type { ChannelKey, Project } from './project-types'
|
||||
import type { ChannelKey, Project, WorkbenchView } from './project-types'
|
||||
|
||||
const { Sider } = Layout
|
||||
const { Title, Text, Paragraph } = Typography
|
||||
|
||||
export function ProjectInspector({ activeProject, activeChannel, selectedItem }: { activeProject: Project; activeChannel: ChannelKey; selectedItem: string }) {
|
||||
export function ProjectInspector({
|
||||
activeView,
|
||||
activeProject,
|
||||
activeChannel,
|
||||
selectedItem,
|
||||
}: {
|
||||
activeView: WorkbenchView
|
||||
activeProject: Project
|
||||
activeChannel: ChannelKey
|
||||
selectedItem: string
|
||||
}) {
|
||||
const workspaceMode = activeView === 'workspace'
|
||||
|
||||
return (
|
||||
<Sider className="inspector" width={360}>
|
||||
<Tabs defaultActiveTab="discussion">
|
||||
<Tabs.TabPane key="discussion" title="讨论"><InspectorDiscussion selectedItem={selectedItem} /></Tabs.TabPane>
|
||||
<Tabs.TabPane key="props" title="属性"><div className="property-list"><Property label="所属项目" value={activeProject.name} /><Property label="频道" value={channels.find((c) => c.key === activeChannel)?.label ?? '概况'} /><Property label="创建人" value="张伟" /><Property label="更新时间" value="2026-07-20 10:30" /></div></Tabs.TabPane>
|
||||
<Tabs.TabPane key="more" title="更多"><Space direction="vertical" className="more-actions"><Button long>复制链接</Button><Button long>标记已处理</Button><Button long status="danger">归档对象</Button></Space></Tabs.TabPane>
|
||||
<Tabs.TabPane key="discussion" title="讨论">
|
||||
<InspectorDiscussion selectedItem={selectedItem} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="props" title="属性">
|
||||
<div className="property-list">
|
||||
<Property label="所属项目" value={workspaceMode ? '全部项目' : activeProject.name} />
|
||||
<Property label="频道" value={workspaceMode ? '工作台总览' : channels.find((channel) => channel.key === activeChannel)?.label ?? '概况'} />
|
||||
<Property label="创建人" value="张伟" />
|
||||
<Property label="更新时间" value="2026-07-20 10:30" />
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="more" title="更多">
|
||||
<Space direction="vertical" className="more-actions">
|
||||
<Button long>复制链接</Button>
|
||||
<Button long>标记已处理</Button>
|
||||
<Button long status="danger">归档对象</Button>
|
||||
</Space>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Sider>
|
||||
)
|
||||
|
||||
@@ -2,18 +2,34 @@ import type { CSSProperties } from 'react'
|
||||
import { Badge, Button, Layout, Space } from '@arco-design/web-react'
|
||||
import { IconDashboard, IconPlus } from '@arco-design/web-react/icon'
|
||||
import { projects } from './project-data'
|
||||
import type { Project } from './project-types'
|
||||
import type { Project, WorkbenchView } from './project-types'
|
||||
|
||||
const { Sider } = Layout
|
||||
|
||||
export function ProjectRail({ activeProject, onSelectProject }: { activeProject: Project; onSelectProject: (project: Project) => void }) {
|
||||
export function ProjectRail({
|
||||
activeProject,
|
||||
activeView,
|
||||
onSelectWorkspace,
|
||||
onSelectProject,
|
||||
}: {
|
||||
activeProject: Project
|
||||
activeView: WorkbenchView
|
||||
onSelectWorkspace: () => void
|
||||
onSelectProject: (project: Project) => void
|
||||
}) {
|
||||
return (
|
||||
<Sider className="project-rail" width={96}>
|
||||
<Button className="dashboard-button" icon={<IconDashboard />} />
|
||||
<Button
|
||||
className={activeView === 'workspace' ? 'dashboard-button active' : 'dashboard-button'}
|
||||
icon={<IconDashboard />}
|
||||
onClick={onSelectWorkspace}
|
||||
aria-label="工作台"
|
||||
title="工作台"
|
||||
/>
|
||||
<Space className="project-stack" direction="vertical" size={12}>
|
||||
{projects.map((project) => (
|
||||
<Badge key={project.id} count={project.badge} dot={false} className={project.urgent ? 'project-badge urgent' : 'project-badge'}>
|
||||
<button className={activeProject.id === project.id ? 'project-button active' : 'project-button'} onClick={() => onSelectProject(project)} style={{ '--project-color': project.color } as CSSProperties} title={project.name}>
|
||||
<button className={activeView === 'project' && activeProject.id === project.id ? 'project-button active' : 'project-button'} onClick={() => onSelectProject(project)} style={{ '--project-color': project.color } as CSSProperties} title={project.name}>
|
||||
{project.short}
|
||||
</button>
|
||||
</Badge>
|
||||
|
||||
@@ -1,34 +1,81 @@
|
||||
import { Badge, Button, Divider, Layout, Space, Tag, Typography } from '@arco-design/web-react'
|
||||
import { IconDashboard, IconMore, IconSettings } from '@arco-design/web-react/icon'
|
||||
import { IconApps, IconDashboard, IconMore, IconSettings } from '@arco-design/web-react/icon'
|
||||
import { channels } from './project-data'
|
||||
import type { ChannelKey, Project } from './project-types'
|
||||
import type { ChannelKey, Project, WorkbenchView } from './project-types'
|
||||
|
||||
const { Sider } = Layout
|
||||
const { Title, Text } = Typography
|
||||
|
||||
export function ProjectSidebar({ activeProject, activeChannel, onSelectChannel, onSelectItem }: { activeProject: Project; activeChannel: ChannelKey; onSelectChannel: (key: ChannelKey) => void; onSelectItem: (title: string) => void }) {
|
||||
export function ProjectSidebar({
|
||||
activeView,
|
||||
activeProject,
|
||||
activeChannel,
|
||||
onSelectChannel,
|
||||
onSelectItem,
|
||||
}: {
|
||||
activeView: WorkbenchView
|
||||
activeProject: Project
|
||||
activeChannel: ChannelKey
|
||||
onSelectChannel: (key: ChannelKey) => void
|
||||
onSelectItem: (title: string) => void
|
||||
}) {
|
||||
const workspaceMode = activeView === 'workspace'
|
||||
|
||||
return (
|
||||
<Sider className="channel-sidebar" width={248}>
|
||||
<div className="project-title"><Space><IconDashboard /><Title heading={5}>{activeProject.name}</Title></Space><Button icon={<IconSettings />} size="mini" /></div>
|
||||
<div className="channel-list">
|
||||
{channels.map((channel) => (
|
||||
<button key={channel.key} className={activeChannel === channel.key ? 'channel-button active' : 'channel-button'} onClick={() => onSelectChannel(channel.key)}>
|
||||
<span className="channel-left">{channel.icon}<Text>{channel.label}</Text></span>
|
||||
{channel.count !== undefined ? <Badge count={channel.count} maxCount={999} /> : <IconMore />}
|
||||
</button>
|
||||
))}
|
||||
<div className="project-title">
|
||||
<Space>
|
||||
{workspaceMode ? <IconApps /> : <IconDashboard />}
|
||||
<Title heading={5}>{workspaceMode ? '工作台' : activeProject.name}</Title>
|
||||
</Space>
|
||||
<Button icon={<IconSettings />} size="mini" />
|
||||
</div>
|
||||
|
||||
<div className="channel-list">
|
||||
{workspaceMode ? (
|
||||
<>
|
||||
<button className="channel-button active">
|
||||
<span className="channel-left"><IconDashboard /><Text>工作台总览</Text></span>
|
||||
<Badge count={4} />
|
||||
</button>
|
||||
<button className="channel-button">
|
||||
<span className="channel-left"><IconApps /><Text>全部项目</Text></span>
|
||||
<IconMore />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
channels.map((channel) => (
|
||||
<button
|
||||
key={channel.key}
|
||||
className={activeChannel === channel.key ? 'channel-button active' : 'channel-button'}
|
||||
onClick={() => onSelectChannel(channel.key)}
|
||||
>
|
||||
<span className="channel-left">{channel.icon}<Text>{channel.label}</Text></span>
|
||||
{channel.count !== undefined ? <Badge count={channel.count} maxCount={999} /> : <IconMore />}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<section className="recent-sessions">
|
||||
<Text className="section-title">最近会话</Text>
|
||||
{['需求评审要点整理', '生成 Q3 营销策略初稿', 'npx 安装 skill 至 codex', '产品定价模型讨论'].map((title, index) => (
|
||||
<button key={title} onClick={() => onSelectItem(title)}><Text>{title}</Text><Text type="secondary">{index === 0 ? '09:15' : index === 1 ? '08:47' : '昨天'}</Text></button>
|
||||
<button key={title} onClick={() => onSelectItem(title)}>
|
||||
<Text>{title}</Text>
|
||||
<Text type="secondary">{index === 0 ? '09:15' : index === 1 ? '08:47' : '昨天'}</Text>
|
||||
</button>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<Divider />
|
||||
<section className="tag-cloud">
|
||||
<Text className="section-title">标签</Text>
|
||||
<Space wrap>{['全部', '需求', '设计', '研发', '运营', 'AI'].map((tag) => <Tag key={tag} color={tag === '全部' ? 'arcoblue' : 'gray'}>{tag}</Tag>)}</Space>
|
||||
<Space wrap>
|
||||
{['全部', '需求', '设计', '研发', '运营', 'AI'].map((tag) => (
|
||||
<Tag key={tag} color={tag === '全部' ? 'arcoblue' : 'gray'}>{tag}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</section>
|
||||
</Sider>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ReactElement } from 'react'
|
||||
|
||||
export type Screen = 'login' | 'workbench'
|
||||
export type Theme = 'light' | 'dark'
|
||||
export type WorkbenchView = 'workspace' | 'project'
|
||||
export type ChannelKey = 'overview' | 'inbox' | 'tasks' | 'ai' | 'notes' | 'cron' | 'research' | 'design'
|
||||
|
||||
export type Project = {
|
||||
|
||||
91
apps/senlinai-acro-react/src/pages/workspace.tsx
Normal file
91
apps/senlinai-acro-react/src/pages/workspace.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Button, Card, Grid, Space, Tag, Typography } from '@arco-design/web-react'
|
||||
import {
|
||||
IconCheckCircleFill,
|
||||
IconDashboard,
|
||||
IconEmail,
|
||||
IconFile,
|
||||
IconRobot,
|
||||
IconRight,
|
||||
IconUserGroup,
|
||||
} from '@arco-design/web-react/icon'
|
||||
import { aiSessions, inbox, notes, projects, tasks } from './projects/project-data'
|
||||
import type { Project } from './projects/project-types'
|
||||
|
||||
const { Row, Col } = Grid
|
||||
const { Title, Text } = Typography
|
||||
|
||||
export function WorkspacePage({ onSelectProject }: { onSelectProject: (project: Project) => void }) {
|
||||
const totalProjectBadges = projects.reduce((sum, project) => sum + project.badge, 0)
|
||||
const urgentProjects = projects.filter((project) => project.urgent).length
|
||||
const workspaceMetrics = [
|
||||
{ title: '项目总数', value: projects.length, delta: `${urgentProjects} 个高优先级`, icon: <IconDashboard />, color: 'arcoblue' },
|
||||
{ title: '全局待处理', value: totalProjectBadges, delta: '跨项目聚合', icon: <IconEmail />, color: 'red' },
|
||||
{ title: '进行中任务', value: tasks.length, delta: '本周持续推进', icon: <IconCheckCircleFill />, color: 'green' },
|
||||
{ title: 'AI 会话', value: aiSessions.length, delta: '队列空闲', icon: <IconRobot />, color: 'purple' },
|
||||
{ title: '知识资料', value: notes.length, delta: '最近 7 天更新', icon: <IconFile />, color: 'cyan' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="workspace-page overview-page">
|
||||
<div className="overview-head">
|
||||
<div>
|
||||
<Text type="secondary">所有项目的统一工作台,汇总项目动态、任务压力和知识资产。</Text>
|
||||
<Title heading={4}>工作台</Title>
|
||||
</div>
|
||||
<Space>
|
||||
<Button icon={<IconUserGroup />}>团队视图</Button>
|
||||
<Button type="primary" icon={<IconRight />}>进入重点项目</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Row gutter={12} className="metric-row">
|
||||
{workspaceMetrics.map((metric) => (
|
||||
<Col span={24 / workspaceMetrics.length} key={metric.title}>
|
||||
<Card className="metric-card" bordered>
|
||||
<Space align="start">
|
||||
<Tag color={metric.color}>{metric.icon}</Tag>
|
||||
<div>
|
||||
<Text type="secondary">{metric.title}</Text>
|
||||
<Title heading={3}>{metric.value}</Title>
|
||||
<Text className="metric-delta">{metric.delta}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<Card className="queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>项目健康度</Title>
|
||||
<Button type="text" size="mini">查看全部项目</Button>
|
||||
</div>
|
||||
{projects.map((project) => (
|
||||
<button key={project.id} className="data-row" onClick={() => onSelectProject(project)}>
|
||||
<span className="row-title"><IconDashboard /> {project.name}</span>
|
||||
<Tag color={project.urgent ? 'red' : 'green'}>{project.urgent ? '高优先级' : '正常'}</Tag>
|
||||
<span>待处理 {project.badge}</span>
|
||||
<span>项目代码 {project.short}</span>
|
||||
<time>今日更新</time>
|
||||
</button>
|
||||
))}
|
||||
</Card>
|
||||
|
||||
<Card className="queue-section" bordered>
|
||||
<div className="section-header">
|
||||
<Title heading={6}>跨项目待处理</Title>
|
||||
<Button type="text" size="mini">打开全局 Inbox</Button>
|
||||
</div>
|
||||
{inbox.slice(0, 4).map((item) => (
|
||||
<div key={item.title} className="data-row">
|
||||
<span className="row-title"><IconEmail /> {item.title}</span>
|
||||
<span>{item.meta}</span>
|
||||
<Tag>{item.tag}</Tag>
|
||||
<span>{item.owner}</span>
|
||||
<time>{item.time}</time>
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user