import React from 'react';
import { parse } from 'qs';
import classnames from 'classnames';
import { isEmpty, get } from 'lodash';
import { renderFilterMap, isAdminPage } from 'utils/index';
import { Button, Divider, Tabs, Typography } from 'antd';
import { UpOutlined, DownOutlined, SyncOutlined } from '@ant-design/icons';
import NotFound from 'components/Cards/NotFound';
import Infos from 'components/Infos';
import Notify from 'components/Notify';
import { toJS } from 'mobx';
import checkItemPolicy from 'resources/skyline/policy';
import ItemActionButtons from 'components/Tables/Base/ItemActionButtons';
import { emptyActionConfig } from 'utils/constants';
import { getPath, getLinkRender } from 'utils/route-map';
import { getValueMapRender, getUnitRender } from 'utils/table';
import styles from './index.less';
export default class DetailBase extends React.Component {
constructor(props, options = {}) {
super(props);
this.options = options;
this.state = {
notFound: false,
collapsed: false,
};
this.init();
}
componentDidMount() {
this.fetchDataWithPolicy();
}
componentDidUpdate(prevProps) {
const { id: oldId } = prevProps.match.params;
if (this.id !== oldId) {
this.handleRefresh(true);
}
}
get params() {
return this.props.match.params || {};
}
get id() {
return this.props.match.params.id;
}
get policy() {
return '';
}
get aliasPolicy() {
return '';
}
get name() {
return '';
}
get routing() {
return this.props.rootStore.routing;
}
get path() {
const { location: { pathname = '' } = {} } = this.props;
return pathname || '';
}
get isAdminPage() {
const { pathname } = this.props.location;
return isAdminPage(pathname);
}
getRouteName(routeName) {
return this.isAdminPage ? `${routeName}Admin` : routeName;
}
getRoutePath(routeName, params = {}, query = {}) {
const realName = this.getRouteName(routeName);
return getPath({ key: realName, params, query });
}
getLinkRender(routeName, value, params = {}, query = {}) {
const realName = this.getRouteName(routeName);
return getLinkRender({ key: realName, params, query, value });
}
get tabs() {
return [];
}
get tab() {
if (this.tabs.length === 0) {
return null;
}
const params = parse(this.routing.location.search.slice(1));
const { tab } = params;
const tabItem = this.tabs.find((it) => it.key === tab);
return tabItem || this.tabs[0];
}
get actionConfigs() {
return emptyActionConfig;
}
get rowActions() {
return [];
}
onCollapsedCallback = () => {};
handleChangeTab = (tab) => {
this.handleFetch({ tab }, true);
};
handleFetch = (params, refresh) => {
this.routing.query(params, refresh);
};
get detailTabs() {
const tabs = [
{
title: 'tab1',
key: 'tab1',
component: null,
},
{
title: 'tab2',
key: 'tab2',
component: null,
},
];
return tabs;
}
get titleLabel() {
return 'ID:';
}
get titleValue() {
return this.params.id;
}
get detailTitle() {
const { collapsed } = this.state;
const { Paragraph } = Typography;
const icon = collapsed ? <DownOutlined /> : <UpOutlined />;
return (
<div>
<span className={styles['title-label']}>{this.titleLabel}</span>
<span className={styles['header-title']}>
<Paragraph style={{ display: 'inherit' }} copyable>
{this.titleValue}
</Paragraph>
</span>
<Divider type="vertical" className={styles['header-divider']} />
<Button onClick={this.goBack} type="link">
{t('Back')}
</Button>
<Button
type="link"
icon={<SyncOutlined />}
onClick={this.handleRefresh}
/>
<Button
onClick={this.handleDetailInfo}
icon={icon}
type="link"
className={styles['header-button']}
/>
</div>
);
}
get className() {
return '';
}
get listUrl() {
return '';
}
get detailData() {
return toJS(this.store.detail) || {};
}
get isLoading() {
return this.store.isLoading;
}
get detailInfos() {
return [];
}
get forceLoadingTabs() {
return [];
}
handleDetailInfo = () => {
const { collapsed } = this.state;
this.setState(
{
collapsed: !collapsed,
},
() => {
this.onCollapsedCallback(!collapsed);
}
);
};
getDesc = (data, dataConf) => {
const { dataIndex, render, valueRender, valueMap, unit } = dataConf;
const value = get(data, dataIndex);
if (render) {
return render(value, data);
}
if (valueRender) {
const renderFunc = renderFilterMap[valueRender];
return renderFunc && renderFunc(value);
}
if (valueMap) {
return getValueMapRender(dataConf)(value);
}
if (unit) {
return getUnitRender(dataConf)(value);
}
if ([undefined, null, ''].includes(value)) {
return '-';
}
return value;
};
getActionData() {
return this.detailData;
}
fetchData = (params, silent) => {
if (this.store.fetchDetail) {
const newParams = {
...this.params,
...(params || {}),
all_projects: this.isAdminPage,
silent,
};
const realParams = this.updateFetchParams(newParams);
this.store.fetchDetail(realParams).catch(this.catch);
}
};
getRouteProps = () => ({});
fetchDataWithPolicy = (silent, params) => {
if (
!checkItemPolicy({
policy: this.policy,
aliasPolicy: this.aliasPolicy,
actionName: this.name,
})
) {
const error = {
message: t("You don't have access to get {name}.", {
name: this.name.toLowerCase(),
}),
status: 403,
};
Notify.errorWithDetail(
error,
t('Unable to get {name} detail.', { name: this.name.toLowerCase() })
);
return;
}
this.fetchData(params, silent);
};
refreshDetailByTab = (silence = true) => {
this.fetchDataWithPolicy(silence);
};
refreshDetailByAction = (silence) => {
this.fetchDataWithPolicy(silence);
};
handleRefresh = () => {
this.fetchDataWithPolicy(false);
};
catch = (e) => {
console.log(e);
const { data, status } = (e || {}).response || e || {};
if (status === 401) {
const title = t('The session has expired, please log in again.');
Notify.errorWithDetail(null, title);
} else if (status === 404) {
this.setState({ notFound: true });
Notify.warn(
t('{name} {id} could not be found.', {
name: this.name.toLowerCase(),
id: this.id,
})
);
} else {
const error = {
message: data,
status,
};
Notify.errorWithDetail(
error,
t('Get {name} detail error.', { name: this.name.toLowerCase() })
);
}
};
goBack = () => {
this.routing.push(this.listUrl);
};
updateFetchParams = (params) => params;
onFinishAction = (success, fail, isDelete) => {
if (success && isDelete) {
this.goBack();
} else {
const silence = !this.forceLoadingTabs.includes(this.tab.key);
this.refreshDetailByAction(silence);
}
this.setState({
inAction: false,
});
};
onClickAction = () => {
this.setState({
inAction: true,
});
};
onCancelAction = () => {
this.setState({
inAction: false,
});
};
init() {
this.store = {
detail: {},
isLoading: true,
};
}
renderDetailInfos() {
const { Paragraph } = Typography;
const { collapsed } = this.state;
if (isEmpty(this.detailData)) {
return (
<Infos
title={this.detailTitle}
descriptions={[]}
loading={this.isLoading}
/>
);
}
const descriptions = collapsed
? []
: this.detailInfos
.filter((it) => !it.hidden)
.map((it) => {
const { title, dataIndex, copyable } = it;
let desc;
if (
this.isLoading ||
!this.detailData ||
isEmpty(this.detailData)
) {
desc = '-';
} else {
desc = this.getDesc(this.detailData, it);
if (desc !== '-') {
if (
copyable ||
dataIndex.toLowerCase().indexOf('id') === 0 ||
dataIndex.toLowerCase().indexOf('_id') >= 0
) {
desc = (
<Paragraph style={{ margin: 0 }} copyable={copyable}>
{desc}
</Paragraph>
);
}
}
}
return {
label: title,
content: desc,
};
});
return (
<Infos
title={this.detailTitle}
descriptions={descriptions}
loading={this.isLoading}
/>
);
}
renderTabComponent(tabItem) {
const { component, key, render } = tabItem;
const { inAction } = this.state;
if (render) {
return render;
}
return component ? (
<tabItem.component
{...this.props}
detail={this.detailData}
detailName={`${this.name}-${this.id}`}
refreshDetail={this.refreshDetailByTab}
inAction={inAction}
/>
) : (
<span>{key}</span>
);
}
renderTab(tabKey, tabItem) {
if (tabKey !== tabItem.key) {
return <Tabs.TabPane tab={tabItem.title} key={tabItem.key} />;
}
return (
<Tabs.TabPane tab={tabItem.title} key={tabItem.key}>
{this.renderTabComponent(tabItem)}
</Tabs.TabPane>
);
}
renderTabs() {
if (isEmpty(this.detailData) || this.isLoading) {
return null;
}
if (!this.tab) {
return null;
}
const tabPanes = this.tabs.map((it) => this.renderTab(this.tab.key, it));
return (
<div className={classnames(styles['tab-wrapper'], this.className)}>
<Tabs activeKey={this.tab.key} onChange={this.handleChangeTab}>
{tabPanes}
</Tabs>
</div>
);
}
renderActions() {
const data = this.getActionData();
if (isEmpty(data) || this.store.isLoading) {
return null;
}
return (
<ItemActionButtons
actions={this.actionConfigs.rowActions || this.actions}
onFinishAction={this.onFinishAction}
item={this.getActionData()}
containerProps={{ isAdminPage: this.isAdminPage }}
isAdminPage={this.isAdminPage}
onClickAction={this.onClickAction}
onCancelAction={this.onCancelAction}
// firstActionClassName={styles['attach-btn']}
/>
);
}
render() {
if (this.state.notFound) {
return <NotFound title={this.name} link={this.listUrl} goList />;
}
return (
<div className={classnames(styles.main, this.className, 'detail-main')}>
<div className={styles['action-wrapper']}>{this.renderActions()}</div>
<div className={styles.header}>{this.renderDetailInfos()}</div>
<div className={styles.tabs}>{this.renderTabs()}</div>
</div>
);
}
}