Skip to content

Commit dd98c38

Browse files
leopuleoPavel910
andauthored
feat(app-aco): add table columns definition via react-properties (#3756)
Co-authored-by: Pavel Denisjuk <[email protected]>
1 parent 4d7e52f commit dd98c38

File tree

128 files changed

+2328
-1368
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+2328
-1368
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { makeAutoObservable } from "mobx";
2+
import { IColumnsVisibilityRepository } from "./IColumnsVisibilityRepository";
3+
import { IColumnsRepository } from "../Columns";
4+
5+
export class ColumnsVisibilityDecorator implements IColumnsRepository {
6+
private columnsVisibilityRepository: IColumnsVisibilityRepository;
7+
private columnsRepository: IColumnsRepository;
8+
9+
constructor(
10+
visibilityRepository: IColumnsVisibilityRepository,
11+
columnsRepository: IColumnsRepository
12+
) {
13+
this.columnsRepository = columnsRepository;
14+
this.columnsVisibilityRepository = visibilityRepository;
15+
makeAutoObservable(this);
16+
}
17+
18+
async init() {
19+
this.columnsRepository.init();
20+
this.columnsVisibilityRepository.init();
21+
}
22+
23+
getColumns() {
24+
const columns = this.columnsRepository.getColumns();
25+
const visibility = this.columnsVisibilityRepository.getVisibility();
26+
27+
return visibility
28+
? columns.map(column => {
29+
return { ...column, visible: visibility[column.name] ?? column.visible };
30+
})
31+
: columns;
32+
}
33+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { ColumnsVisibilityPresenter } from "./ColumnsVisibilityPresenter";
2+
import { IColumnsVisibilityGateway } from "../gateways";
3+
import { ColumnConfig } from "~/config/table/Column";
4+
import { Column, ColumnsPresenter, ColumnsRepository } from "../Columns";
5+
import { ColumnsVisibilityRepository } from "./ColumnsVisibilityRepository";
6+
import { ColumnsVisibilityDecorator } from "./ColumnsVisibilityDecorator";
7+
import { ColumnsVisibilityUpdater } from "~/components/Table/components/Table/ColumnVisibility/ColumnsVisibilityUpdater";
8+
9+
const defaultGateway: IColumnsVisibilityGateway = {
10+
get: jest.fn(),
11+
set: jest.fn()
12+
};
13+
14+
const createMockGateway = ({
15+
get,
16+
set
17+
}: Partial<IColumnsVisibilityGateway>): IColumnsVisibilityGateway => ({
18+
...defaultGateway,
19+
...(get && { get }),
20+
...(set && { set })
21+
});
22+
23+
describe("ColumnsVisibilityPresenter", () => {
24+
const columnConfigs: ColumnConfig[] = [
25+
{
26+
cell: "Id Cell Content",
27+
className: "id-class",
28+
header: "Id",
29+
hideable: true,
30+
name: "id",
31+
resizable: true,
32+
size: 200,
33+
sortable: true,
34+
visible: true
35+
},
36+
{
37+
cell: "Title Cell Content",
38+
className: "title-class",
39+
header: "Title",
40+
hideable: true,
41+
name: "title",
42+
resizable: true,
43+
size: 200,
44+
sortable: true,
45+
visible: true
46+
},
47+
{
48+
cell: "Date Cell Content",
49+
className: "date-class",
50+
header: "Date",
51+
hideable: true,
52+
name: "date",
53+
resizable: true,
54+
size: 200,
55+
sortable: true,
56+
visible: false
57+
}
58+
];
59+
60+
beforeEach(() => {
61+
jest.clearAllMocks();
62+
});
63+
64+
it("should return the columns visibility from the column config", async () => {
65+
// Let's create repositories and presenters
66+
const columnsRepo = new ColumnsRepository(
67+
columnConfigs.map(config => Column.createFromConfig(config))
68+
);
69+
const visibilityRepo = new ColumnsVisibilityRepository(defaultGateway);
70+
const repo = new ColumnsVisibilityDecorator(visibilityRepo, columnsRepo);
71+
72+
const columnsPresenter = new ColumnsPresenter(repo);
73+
const columnsVisibilityPresenter = new ColumnsVisibilityPresenter(columnsPresenter);
74+
75+
// Let's init the ColumnsPresenter
76+
await columnsPresenter.init();
77+
78+
expect(defaultGateway.get).toBeCalledTimes(1);
79+
80+
expect(columnsVisibilityPresenter.vm).toEqual({
81+
columnsVisibility: {
82+
id: true,
83+
title: true,
84+
date: false
85+
}
86+
});
87+
});
88+
89+
it("should return the columns visibility from both the column configs and the gateway", async () => {
90+
// Let's create a mocked gateway
91+
const gateway = createMockGateway({
92+
get: jest.fn().mockImplementation(() => ({ title: false }))
93+
});
94+
95+
// Let's create repositories and presenters
96+
const columnsRepo = new ColumnsRepository(
97+
columnConfigs.map(config => Column.createFromConfig(config))
98+
);
99+
const visibilityRepo = new ColumnsVisibilityRepository(gateway);
100+
const repo = new ColumnsVisibilityDecorator(visibilityRepo, columnsRepo);
101+
102+
const columnsPresenter = new ColumnsPresenter(repo);
103+
const columnsVisibilityPresenter = new ColumnsVisibilityPresenter(columnsPresenter);
104+
105+
// Let's init the ColumnsPresenter
106+
await columnsPresenter.init();
107+
108+
expect(gateway.get).toBeCalledTimes(1);
109+
110+
expect(columnsVisibilityPresenter.vm).toEqual({
111+
columnsVisibility: {
112+
id: true,
113+
title: false, // title is defined true in the config, but defined false in the gateway
114+
date: false
115+
}
116+
});
117+
});
118+
119+
it("should be able to update the visibility via `ColumnsVisibilityUpdater`", async () => {
120+
// Let's create repositories and presenters
121+
const columnsRepo = new ColumnsRepository(
122+
columnConfigs.map(config => Column.createFromConfig(config))
123+
);
124+
const visibilityRepo = new ColumnsVisibilityRepository(defaultGateway);
125+
const repo = new ColumnsVisibilityDecorator(visibilityRepo, columnsRepo);
126+
127+
const columnsPresenter = new ColumnsPresenter(repo);
128+
const columnsVisibilityPresenter = new ColumnsVisibilityPresenter(columnsPresenter);
129+
130+
// Let's create ColumnsVisibilityUpdater
131+
const columnsVisibilityUpdater = new ColumnsVisibilityUpdater(visibilityRepo);
132+
133+
// Let's init the ColumnsPresenter
134+
await columnsPresenter.init();
135+
136+
// Let's check the initial state
137+
const initial = columnsVisibilityPresenter.vm.columnsVisibility;
138+
expect(initial).toEqual({
139+
id: true,
140+
title: true,
141+
date: false
142+
});
143+
144+
// Let's update the visibility
145+
const updater = jest.fn().mockImplementation(current => ({ ...current, title: false }));
146+
await columnsVisibilityUpdater.update(updater);
147+
148+
expect(columnsVisibilityPresenter.vm).toEqual({
149+
columnsVisibility: {
150+
id: true,
151+
title: false,
152+
date: false
153+
}
154+
});
155+
});
156+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { makeAutoObservable } from "mobx";
2+
import { ColumnsPresenter } from "../Columns/ColumnsPresenter";
3+
4+
export class ColumnsVisibilityPresenter {
5+
private columnsPresenter: ColumnsPresenter;
6+
7+
constructor(columnsPresenter: ColumnsPresenter) {
8+
this.columnsPresenter = columnsPresenter;
9+
makeAutoObservable(this);
10+
}
11+
12+
get vm() {
13+
return {
14+
columnsVisibility: this.getColumnsVisibility()
15+
};
16+
}
17+
18+
private getColumnsVisibility() {
19+
return this.columnsPresenter.vm.columns.reduce((acc, column) => {
20+
return { ...acc, [column.name]: column.visible };
21+
}, {});
22+
}
23+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { makeAutoObservable, runInAction } from "mobx";
2+
import { IColumnsVisibilityGateway } from "../gateways/IColumnsVisibilityGateway";
3+
import { IColumnsVisibilityRepository } from "./IColumnsVisibilityRepository";
4+
5+
export class ColumnsVisibilityRepository implements IColumnsVisibilityRepository {
6+
private gateway: IColumnsVisibilityGateway;
7+
private state: Record<string, boolean>;
8+
9+
constructor(gateway: IColumnsVisibilityGateway) {
10+
this.gateway = gateway;
11+
this.state = {};
12+
makeAutoObservable(this);
13+
}
14+
15+
async init() {
16+
const visibility = await this.gateway.get();
17+
runInAction(() => {
18+
this.state = visibility ?? {};
19+
});
20+
}
21+
22+
getVisibility() {
23+
return this.state;
24+
}
25+
26+
async update(value: Record<string, boolean>) {
27+
const newState = {
28+
...this.state,
29+
...value
30+
};
31+
32+
await this.gateway.set(newState);
33+
34+
runInAction(() => {
35+
this.state = newState;
36+
});
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ColumnsVisibilityRepository } from "./ColumnsVisibilityRepository";
2+
import { IColumnsVisibilityGateway } from "../gateways/IColumnsVisibilityGateway";
3+
4+
class ColumnsVisibilityRepositoryFactory {
5+
private cache: Map<string, ColumnsVisibilityRepository> = new Map();
6+
7+
getRepository(namespace: string, gateway: IColumnsVisibilityGateway) {
8+
const cacheKey = this.getCacheKey(namespace);
9+
10+
if (!this.cache.has(cacheKey)) {
11+
this.cache.set(cacheKey, new ColumnsVisibilityRepository(gateway));
12+
}
13+
14+
return this.cache.get(cacheKey) as ColumnsVisibilityRepository;
15+
}
16+
17+
private getCacheKey(namespace: string) {
18+
return namespace;
19+
}
20+
}
21+
22+
export const columnsVisibilityRepositoryFactory = new ColumnsVisibilityRepositoryFactory();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { makeAutoObservable } from "mobx";
2+
import { IColumnsVisibilityRepository } from "./IColumnsVisibilityRepository";
3+
import { IColumnsVisibilityUpdater } from "./IColumnsVisibilityUpdater";
4+
import { OnColumnVisibilityChange } from "@webiny/ui/DataTable";
5+
6+
export class ColumnsVisibilityUpdater implements IColumnsVisibilityUpdater {
7+
private repository: IColumnsVisibilityRepository;
8+
9+
constructor(repository: IColumnsVisibilityRepository) {
10+
this.repository = repository;
11+
makeAutoObservable(this);
12+
}
13+
14+
public update: OnColumnVisibilityChange = async updaterOrValue => {
15+
const currentVisibility = this.repository.getVisibility();
16+
let newVisibility = currentVisibility;
17+
18+
if (typeof updaterOrValue === "function") {
19+
newVisibility = updaterOrValue(currentVisibility || {});
20+
}
21+
22+
this.repository.update(newVisibility || {});
23+
};
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface IColumnsVisibilityRepository {
2+
init(): Promise<void>;
3+
getVisibility(): Record<string, boolean> | undefined;
4+
update(value: Record<string, boolean>): Promise<void>;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { OnColumnVisibilityChange } from "@webiny/ui/DataTable";
2+
3+
export interface IColumnsVisibilityUpdater {
4+
update: OnColumnVisibilityChange;
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * from "./IColumnsVisibilityRepository";
2+
export * from "./IColumnsVisibilityUpdater";
3+
export * from "./ColumnsVisibilityDecorator";
4+
export * from "./ColumnsVisibilityPresenter";
5+
export * from "./ColumnsVisibilityRepository";
6+
export * from "./ColumnsVisibilityRepositoryFactory";
7+
export * from "./ColumnsVisibilityUpdater";
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ReactElement } from "react";
2+
import { ColumnConfig } from "~/config/table/Column";
3+
4+
export interface ColumnDTO {
5+
cell: string | ReactElement;
6+
className: string;
7+
header: string | number | JSX.Element;
8+
hideable: boolean;
9+
name: string;
10+
resizable: boolean;
11+
size: number;
12+
sortable: boolean;
13+
visible: boolean;
14+
}
15+
16+
export class Column {
17+
public cell: string | ReactElement;
18+
public className: string;
19+
public header: string | number | JSX.Element;
20+
public hideable: boolean;
21+
public name: string;
22+
public resizable: boolean;
23+
public size: number;
24+
public sortable: boolean;
25+
public visible: boolean;
26+
27+
static createFromConfig(config: ColumnConfig) {
28+
return new Column(config);
29+
}
30+
31+
protected constructor(data: {
32+
name: string;
33+
header: string | number | JSX.Element;
34+
cell: string | ReactElement;
35+
size?: number;
36+
className?: string;
37+
hideable?: boolean;
38+
sortable?: boolean;
39+
resizable?: boolean;
40+
visible?: boolean;
41+
}) {
42+
this.name = data.name;
43+
this.header = data.header;
44+
this.cell = data.cell;
45+
this.size = data.size || 200;
46+
this.className = data.className || "";
47+
this.hideable = data.hideable ?? true;
48+
this.sortable = data.sortable ?? false;
49+
this.resizable = data.resizable ?? true;
50+
this.visible = data.visible ?? true;
51+
}
52+
}

0 commit comments

Comments
 (0)