Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(common): add card list and refactor msp overview #2268

Merged
merged 2 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions shell/app/common/components/card-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) 2021 Terminus, Inc.
//
// This program is free software: you can use, redistribute, and/or modify
// it under the terms of the GNU Affero General Public License, version 3
// or later ("AGPL"), as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import React from 'react';
import { Col, Row, Spin } from 'antd';
import { RowProps } from 'antd/es/row';
import { ColProps } from 'antd/es/col';
import classnames from 'classnames';
import EmptyHolder from 'common/components/empty-holder';

interface CardColumnsProps<T> {
dataIndex: keyof T;
colProps?: ColProps;
render?: (text: any, record: T, index: number) => React.ReactNode;
children?: {
rowProps?: RowProps;
columns: CardColumnsProps<T>[];
};
}

interface IProps<T = Record<string, any>> {
size?: 'default' | 'small' | 'large';
loading?: boolean;
rowKey?: string | ((record: T) => string);
rowClick?: (record: T) => void;
dataSource: T[];
slot?: React.ReactNode;
rowClassName?: string;
columns: CardColumnsProps<T>[];
emptyHolder?: React.ReactNode;
}

const renderChild = <T,>(record: T, columns: CardColumnsProps<T>[], index: number) => {
return columns.map((column) => {
let nodes: React.ReactNode = record[column.dataIndex];
if (column.render) {
nodes = column.render(nodes, record, index);
}
if (column.children?.columns.length) {
nodes = (
<Row className="flex-1" gutter={8} {...column.children.rowProps}>
{renderChild<T>(record, column.children.columns, index)}
</Row>
);
}
return (
<Col key={column.dataIndex as string} span={12} {...column.colProps}>
{nodes}
</Col>
);
});
};

const CardList = <T,>({
loading,
dataSource,
rowKey = 'key',
rowClassName,
columns,
rowClick,
slot,
size = 'default',
emptyHolder,
}: IProps<T>) => {
return (
<div className="card-list flex flex-1 flex-col bg-white shadow pb-2">
<div className="card-list-header px-4 py-2 h-12 bg-lotion flex justify-between items-center">
<div>{slot}</div>
</div>
<div className="card-list-body px-2 mt-2">
<Spin spinning={!!loading}>
{dataSource.length
? dataSource.map((record, index) => {
let rowId;
if (typeof rowKey === 'function') {
rowId = rowKey(record);
} else {
rowId = record[rowKey];
}
const rowClass = classnames(
'card-shadow mb-4 mx-2 px-4 rounded-sm transition-all duration-300 hover:bg-grey',
{
'py-8': size === 'large',
'py-6': size === 'default',
'py-4': size === 'small',
[rowClassName as string]: !!rowClassName,
'cursor-pointer': rowClick,
},
);
return (
<Row
onClick={() => {
rowClick?.(record);
}}
key={rowId}
className={rowClass}
>
{renderChild<T>(record, columns, index)}
</Row>
);
})
: emptyHolder || <EmptyHolder relative />}
</Spin>
</div>
</div>
);
};

export default CardList;
export { CardColumnsProps };
2 changes: 2 additions & 0 deletions shell/app/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,5 @@ export { default as Intro } from './components/intro';
export { default as ErdaAlert } from './components/erda-alert';
export { default as Badge } from './components/badge';
export type { IBadgeProps } from './components/badge';
export { default as CardList } from './components/card-list';
export type { CardColumnsProps } from './components/card-list';
158 changes: 84 additions & 74 deletions shell/app/modules/msp/pages/micro-service/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import React from 'react';
import { Col, Input, Row, Spin, Tag } from 'antd';
import { Input, Tag } from 'antd';
import { useUpdate } from 'common/use-hooks';
import { getMspProjectList } from 'msp/services';
import EmptyHolder from 'common/components/empty-holder';
import ErdaIcon from 'common/components/erda-icon';
import { fromNow, goTo } from 'common/utils';
import { debounce, last } from 'lodash';
Expand All @@ -28,6 +27,7 @@ import middleImg from 'app/images/msp/microservice-governance-middle.svg';
import bottomImg from 'app/images/msp/microservice-governance-bottom.svg';
import backgroundImg from 'app/images/msp/microservice-governance-background.svg';
import decorationImg from 'app/images/msp/microservice-governance-decoration.svg';
import CardList, { CardColumnsProps } from 'common/components/card-list';
import i18n from 'i18n';
import './overview.scss';

Expand Down Expand Up @@ -56,6 +56,29 @@ const iconMap: {
},
};

const metric: { dataIndex: keyof MS_INDEX.IMspProject; name: string; renderData: (data: any) => React.ReactNode }[] = [
{
dataIndex: 'relationship',
name: i18n.t('env'),
renderData: (data: MS_INDEX.IMspRelationship[]) => data.length,
},
{
dataIndex: 'serviceCount',
name: i18n.t('service'),
renderData: (data: number) => data ?? 0,
},
{
dataIndex: 'last24hAlertCount',
name: i18n.t('msp:last 1 day alarm'),
renderData: (data: number) => data ?? 0,
},
{
dataIndex: 'lastActiveTime',
name: i18n.t('msp:last active time'),
renderData: (data: number) => (data ? fromNow(data) : '-'),
},
];

const Overview = () => {
const [{ data, loading, filterKey }, updater] = useUpdate<IState>({
data: [],
Expand Down Expand Up @@ -95,6 +118,52 @@ const Overview = () => {
return data.filter((item) => item.displayName.toLowerCase().includes(filterKey));
}, [data, filterKey]);

const columns: CardColumnsProps<MS_INDEX.IMspProject>[] = [
{
dataIndex: 'displayName',
colProps: {
className: 'flex items-center',
},
render: (displayName: string, { logo, desc, type }) => {
const { icon, color, tag } = iconMap[type];
return (
<>
<div className="w-14 h-14 mr-2">
{logo ? <img src={logo} width={56} height={56} /> : <ErdaIcon type={icon} size={56} />}
</div>
<div>
<p className="mb-0 font-medium text-xl leading-8">{displayName}</p>
<Tag className="mb-0.5 text-xs leading-5 border-0" color={color}>
{tag}
</Tag>
<div className="text-xs leading-5 desc">{desc || i18n.t('no description yet')}</div>
</div>
</>
);
},
},
{
dataIndex: 'id',
colProps: {
className: 'flex items-center',
},
children: {
columns: metric.map((item) => ({
dataIndex: item.dataIndex,
colProps: {
span: 6,
},
render: (text) => (
<>
<p className="mb-0 text-xl leading-8 font-number">{item.renderData(text)}</p>
<p className="mb-0 text-xs leading-5 desc">{item.name}</p>
</>
),
})),
},
},
];

return (
<div className="msp-overview p-6 flex flex-col pt-0">
<div className="msp-overview-header relative overflow-hidden flex content-center justify-center pl-4 flex-col">
Expand All @@ -120,87 +189,28 @@ const Overview = () => {
<img src={decorationImg} className="absolute decoration-img top" />
</div>
</div>
<div className="flex flex-1 flex-col min-h-0 bg-white shadow pb-2">
<div className="px-4 pt-2 bg-lotion">
<CardList<MS_INDEX.IMspProject>
rowKey="id"
size="large"
loading={loading}
columns={columns}
dataSource={list}
rowClick={({ relationship, id }) => {
handleClick(relationship, id);
}}
slot={
<Input
prefix={<ErdaIcon type="search1" />}
bordered={false}
allowClear
placeholder={i18n.t('msp:search by project name')}
className="bg-hover-gray-bg mb-3 w-72"
className="bg-hover-gray-bg w-72"
onChange={(e) => {
handleSearch(e.target.value);
}}
/>
</div>
<div className="px-2 flex-1 overflow-y-auto">
<Spin spinning={loading}>
{list.length ? (
list.map(
({
type,
desc,
displayName,
id,
relationship,
serviceCount,
last24hAlertCount,
lastActiveTime,
logo,
}) => {
const { icon, color, tag } = iconMap[type];
return (
<Row
key={id}
className="project-item card-shadow mb-2 mx-2 px-4 flex py-8 rounded-sm cursor-pointer transition-all duration-300 hover:bg-grey"
onClick={() => {
handleClick(relationship, id);
}}
>
<Col span={12} className="flex items-center">
<div className="w-14 h-14 mr-2">
{logo ? <img src={logo} width={56} height={56} /> : <ErdaIcon type={icon} size={56} />}
</div>
<div>
<p className="mb-0 font-medium text-xl leading-8">{displayName}</p>
<Tag className="mb-0.5 text-xs leading-5 border-0" color={color}>
{tag}
</Tag>
<div className="text-xs leading-5 desc">{desc || i18n.t('no description yet')}</div>
</div>
</Col>
<Col span={12} className="flex items-center">
<Row gutter={8} className="flex-1">
<Col span={6}>
<p className="mb-0 text-xl leading-8 font-number">{relationship.length}</p>
<p className="mb-0 text-xs leading-5 desc">{i18n.t('env')}</p>
</Col>
<Col span={6}>
<p className="mb-0 text-xl leading-8 font-number">{serviceCount ?? 0}</p>
<p className="mb-0 text-xs leading-5 desc">{i18n.t('service')}</p>
</Col>
<Col span={6}>
<p className="mb-0 text-xl leading-8 font-number">{last24hAlertCount ?? 0}</p>
<p className="mb-0 text-xs leading-5 desc">{i18n.t('msp:last 1 day alarm')}</p>
</Col>
<Col span={6}>
<p className="mb-0 text-xl leading-8 font-number">
{lastActiveTime ? fromNow(lastActiveTime) : '-'}
</p>
<p className="mb-0 text-xs leading-5 desc">{i18n.t('msp:last active time')}</p>
</Col>
</Row>
</Col>
</Row>
);
},
)
) : (
<EmptyHolder relative />
)}
</Spin>
</div>
</div>
}
/>
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion shell/app/styles/_color.scss
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ $color-input-bg: #efeef0;

// color new list
$color-box-shadow: rgba($color-text-sub, 0.1);
$color-card-shadow: rgba(85, 77, 104, 0.08);
$color-card-shadow: rgba(48, 38, 71, 0.16);
$color-card-hover-shadow: rgba(85, 77, 104, 0.16);

// new assistant color
Expand Down
4 changes: 2 additions & 2 deletions shell/app/styles/util.scss
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,10 @@
}

.card-shadow {
box-shadow: 0 2px 4px 0 $color-card-shadow;
box-shadow: 0 1px 4px 0 $color-card-shadow;

&:hover {
box-shadow: 0 2px 4px 0 $color-card-hover-shadow;
box-shadow: 0 2px 4px 0 $color-card-shadow;
}
}

Expand Down