Skip to content

Commit d3111ce

Browse files
TobiasKoehlerMarcusNotheis
authored andcommitted
feat(Analytical Table) Initial Implementation (#23)
1 parent 5f56a28 commit d3111ce

File tree

21 files changed

+3185
-13
lines changed

21 files changed

+3185
-13
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,8 @@
140140
"prettier -config ./prettier.config.js --write",
141141
"git add"
142142
]
143+
},
144+
"dependencies": {
145+
"react-table": "6.8.6"
143146
}
144147
}

packages/fiori3/__karma_snapshots__/AnalyticalTable.md

Lines changed: 1361 additions & 0 deletions
Large diffs are not rendered by default.

packages/fiori3/__karma_snapshots__/Table.md

Lines changed: 692 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React from 'react';
2+
import { AnalyticalTable } from '../../lib/AnalyticalTable';
3+
import { mountThemedComponent } from '@shared/tests/utils';
4+
import { matchSnapshot } from 'chai-karma-snapshot';
5+
import { expect, use } from 'chai';
6+
7+
const columns = [
8+
{
9+
Header: 'Name',
10+
accessor: 'name' // String-based value accessors!
11+
},
12+
{
13+
Header: 'Age',
14+
accessor: 'age'
15+
},
16+
{
17+
Header: 'Friend Name',
18+
accessor: 'friend.name' // Custom value accessors!
19+
},
20+
{
21+
Header: () => <span>Friend Age</span>, // Custom header components!
22+
accessor: 'friend.age'
23+
}
24+
];
25+
26+
const data = [
27+
{
28+
name: 'Fra',
29+
age: 40,
30+
friend: {
31+
name: 'MAR',
32+
age: 28
33+
}
34+
},
35+
{
36+
name: 'bla',
37+
age: 20,
38+
friend: {
39+
name: 'Nei',
40+
age: 50
41+
}
42+
}
43+
];
44+
45+
use(matchSnapshot);
46+
47+
describe('AnalyticalTable', () => {
48+
it('test Asc desc', () => {
49+
const wrapper = mountThemedComponent(
50+
<AnalyticalTable showPagination data={data} title={'Test'} columns={columns} />
51+
);
52+
53+
// test asc function inside the popover element
54+
let component = wrapper
55+
.find('ui5-li')
56+
.at(1)
57+
.instance();
58+
// @ts-ignore
59+
component.onclick({});
60+
61+
// test desc function inside the popover element
62+
component = wrapper
63+
.find('ui5-li')
64+
.at(0)
65+
.instance();
66+
// @ts-ignore
67+
component.onclick({});
68+
69+
expect(wrapper.debug()).to.matchSnapshot();
70+
});
71+
it('pagination', () => {
72+
const wrapper = mountThemedComponent(
73+
<AnalyticalTable defaultPageSize={1} showPagination data={data} title={'Test'} columns={columns} />
74+
);
75+
76+
// test the right arrow of the pagination
77+
78+
let component = wrapper
79+
.find('ui5-link')
80+
.at(3)
81+
.instance();
82+
// @ts-ignore
83+
component.onclick({});
84+
85+
// test the right page number link of the pagination
86+
component = wrapper
87+
.find('ui5-link')
88+
.at(2)
89+
.instance();
90+
// @ts-ignore
91+
component.onclick({});
92+
93+
// test the left page number link of the pagination
94+
component = wrapper
95+
.find('ui5-link')
96+
.at(1)
97+
.instance();
98+
// @ts-ignore
99+
component.onclick({});
100+
101+
// test the left arrow of the pagination
102+
component = wrapper
103+
.find('ui5-link')
104+
.at(0)
105+
.instance();
106+
// @ts-ignore
107+
component.onclick({});
108+
109+
expect(wrapper.debug()).to.matchSnapshot();
110+
});
111+
});
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import React, { Component, ReactNode, ReactNodeArray } from 'react';
2+
import { ClassProps } from '../../../interfaces/ClassProps';
3+
import { Icon } from '../../../lib/Icon';
4+
import { ColumnHeaderModal } from '../columnHeaderModal/index';
5+
import { JSSTheme } from '../../../interfaces/JSSTheme';
6+
import { Event, StyleClassHelper } from '@fiori-for-react/utils';
7+
import { fonts, withStyles } from '@fiori-for-react/styles';
8+
9+
export interface ColumnType {
10+
filterable: boolean;
11+
sortable: boolean;
12+
show: boolean;
13+
id: string;
14+
Filter: JSX.Element;
15+
}
16+
17+
export interface ColumnHeaderProps {
18+
firstColumn: boolean;
19+
defaultSortDesc: boolean;
20+
onFilteredChange: (event: Event) => void;
21+
children: ReactNode | ReactNodeArray;
22+
}
23+
24+
interface ColumnHeaderPropsInternal extends ColumnHeaderProps, ClassProps {
25+
style?: object;
26+
toggleSort: () => any;
27+
sorted: any[];
28+
column: ColumnType;
29+
filtered: any[];
30+
sortable: boolean;
31+
filterable: boolean;
32+
}
33+
34+
interface ColumnHeaderState {
35+
modalOpen: boolean;
36+
}
37+
38+
const calcPaddingLeft = (props) => {
39+
return props.firstColumn ? '1rem !important' : '0.5rem !important';
40+
};
41+
42+
const styles = ({ parameters }: JSSTheme) => ({
43+
header: {
44+
borderRight: `1px solid ${parameters.sapUiListBorderColor} !important`,
45+
paddingLeft: calcPaddingLeft,
46+
paddingRight: '0.5rem',
47+
display: 'flex',
48+
justifyContent: 'begin',
49+
alignItems: 'center',
50+
textAlign: 'left',
51+
fontFamily: fonts.sapUiFontFamily,
52+
fontSize: fonts.sapMFontMediumSize,
53+
fontWeight: 'normal',
54+
color: parameters.sapUiListTextColor,
55+
background: parameters.sapUiListHeaderBackground
56+
},
57+
iconContainer: {
58+
display: 'inline-block',
59+
position: 'absolute',
60+
color: parameters.sapUiContentIconColor,
61+
right: '0',
62+
marginRight: '0.5rem',
63+
'& :last-child': {
64+
marginLeft: '0.25rem'
65+
}
66+
}
67+
});
68+
69+
@withStyles(styles)
70+
export class ColumnHeader extends Component<ColumnHeaderProps, ColumnHeaderState> {
71+
getCurrentSort() {
72+
const { sorted, column } = this.props as ColumnHeaderPropsInternal;
73+
const sort = sorted.find((c) => c.id === column.id);
74+
return sort ? sort.desc : null;
75+
}
76+
77+
doSorting = (condition) => {
78+
const { toggleSort } = this.props as ColumnHeaderPropsInternal;
79+
if (condition) {
80+
toggleSort();
81+
} else {
82+
toggleSort();
83+
setTimeout(toggleSort, 0);
84+
}
85+
};
86+
87+
sortAscending = () => {
88+
const { defaultSortDesc } = this.props;
89+
const desc = this.getCurrentSort();
90+
if (desc !== null) {
91+
if (!desc) {
92+
return;
93+
}
94+
this.doSorting(desc);
95+
} else {
96+
this.doSorting(!defaultSortDesc);
97+
}
98+
};
99+
100+
sortDescending = () => {
101+
const { defaultSortDesc } = this.props;
102+
const desc = this.getCurrentSort();
103+
if (desc !== null) {
104+
if (desc) {
105+
return;
106+
}
107+
this.doSorting(!desc);
108+
} else {
109+
this.doSorting(defaultSortDesc);
110+
}
111+
};
112+
113+
// private onFilterChange = (e) => {
114+
// const { column, filtered } = this.props as ColumnHeaderPropsInternal;
115+
// const columnId = column.id;
116+
// const currentFilters = filtered.filter((item) => item.id !== columnId);
117+
// if (e) {
118+
// currentFilters.push({
119+
// id: columnId,
120+
// value: e.getParameter('value')
121+
// });
122+
// }
123+
// this.props.onFilteredChange(Event.of(this, e.getOriginalEvent(), { currentFilters, column }));
124+
// };
125+
126+
private get openBy() {
127+
const { classes, children, sorted, column, filtered } = this.props as ColumnHeaderPropsInternal;
128+
129+
if (!column) return null;
130+
131+
const sort = sorted.find((c) => c.id === column.id);
132+
const filter = filtered.find((c) => c.id === column.id);
133+
const desc = sort ? sort.desc : null;
134+
135+
const classNames = StyleClassHelper.of('rt-th', classes.header);
136+
137+
if (!column.show) {
138+
classNames.put('-hidden');
139+
}
140+
141+
const sortingIcon = sort ? <Icon src={desc ? 'sort-descending' : 'sort-ascending'} /> : null;
142+
const filterIcon = filter ? <Icon src={'filter'} /> : null;
143+
144+
return (
145+
<div className={classNames.valueOf()}>
146+
{children}
147+
<div className={classes.iconContainer}>
148+
{filterIcon}
149+
{sortingIcon}
150+
</div>
151+
</div>
152+
);
153+
}
154+
155+
render() {
156+
const { column, filtered, filterable } = this.props as ColumnHeaderPropsInternal;
157+
158+
if (!column) return null;
159+
160+
const filter = filtered.find((c) => c.id === column.id);
161+
162+
return (
163+
<ColumnHeaderModal
164+
openBy={this.openBy}
165+
showFilter={filterable}
166+
sortAscending={this.sortAscending}
167+
sortDescending={this.sortDescending}
168+
column={column}
169+
FilterComponent={column.Filter}
170+
filter={filter}
171+
// onFilterChange={this.onFilterChange}
172+
/>
173+
);
174+
}
175+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { Component, ReactNode } from 'react';
2+
import { ResponsivePopover } from '../../../lib/ResponsivePopover';
3+
import { List } from '../../../lib/List';
4+
import { StandardListItem } from '../../../lib/StandardListItem';
5+
import { ClassProps } from '../../../interfaces/ClassProps';
6+
import { ColumnType } from '../columnHeader';
7+
import { Event } from '@fiori-for-react/utils';
8+
import { withStyles } from '@fiori-for-react/styles';
9+
import { ListItemTypes } from '../../../lib/ListItemTypes';
10+
import { PlacementType } from '../../../lib/PlacementType';
11+
import { PopoverHorizontalAlign } from '../../../lib/PopoverHorizontalAlign';
12+
import { JSSTheme } from '../../../interfaces/JSSTheme';
13+
14+
const styles = ({ parameters }: JSSTheme) => ({
15+
modalRoot: {
16+
border: 'none',
17+
borderRadius: '0.25rem',
18+
boxShadow: parameters.sapUiShadowLevel1,
19+
'&:focus': {
20+
outline: 'none'
21+
}
22+
}
23+
});
24+
25+
export interface ColumnHeaderModalProperties {
26+
openBy: ReactNode;
27+
showSort?: boolean;
28+
showFilter?: boolean;
29+
sortAscending: (event: Event) => void;
30+
sortDescending: (event: Event) => void;
31+
FilterComponent: any;
32+
filter: any;
33+
column: ColumnType;
34+
}
35+
36+
interface ColumnHeaderModalInternalProperties extends ColumnHeaderModalProperties, ClassProps {}
37+
38+
@withStyles(styles)
39+
export class ColumnHeaderModal extends Component<ColumnHeaderModalProperties> {
40+
static defaultProps = {
41+
showSort: true,
42+
showFilter: false,
43+
filter: null
44+
};
45+
46+
private handleSort = (e) => {
47+
const sortType = e.getParameter('item').getAttribute('data-sort');
48+
if (sortType === 'asc') {
49+
this.props.sortAscending(Event.of(this, e.getOriginalEvent()));
50+
} else {
51+
this.props.sortDescending(Event.of(this, e.getOriginalEvent()));
52+
}
53+
};
54+
55+
render() {
56+
const { showSort } = this.props as ColumnHeaderModalInternalProperties;
57+
58+
return (
59+
<ResponsivePopover
60+
openByStyle={{ flex: '100 0 auto', width: '100px' }}
61+
openBy={this.props.openBy}
62+
hideHeader
63+
hideArrow
64+
horizontalAlign={PopoverHorizontalAlign.Left}
65+
placementType={PlacementType.Bottom}
66+
>
67+
<List onItemPress={this.handleSort}>
68+
{showSort && (
69+
<StandardListItem type={ListItemTypes.Active} icon={'sap-icon://sort-ascending'} data-sort={'asc'}>
70+
Sort Ascending
71+
</StandardListItem>
72+
)}
73+
{showSort && (
74+
<StandardListItem type={ListItemTypes.Active} icon={'sap-icon://sort-descending'} data-sort={'desc'}>
75+
Sort Descending
76+
</StandardListItem>
77+
)}
78+
</List>
79+
</ResponsivePopover>
80+
);
81+
}
82+
}

0 commit comments

Comments
 (0)