Подключен первый виджет kanban_by_tree к дашбордам

This commit is contained in:
Pavel Gnedov 2023-11-03 07:44:36 +07:00
parent 05b6364ae5
commit ae14189cd3
10 changed files with 327 additions and 13 deletions

View file

@ -11,7 +11,9 @@ export const DashboardPage = (): JSX.Element => {
id: id,
loaded: false,
});
Store.DashboardLoadData(store);
Store.DashboardLoadData(store).then(() => {
return Store.LoadDataForWidgets(store);
});
return <Dashboard store={store} />;
};

View file

@ -9,14 +9,58 @@ type _DataLoaderParams = Record<string, any> | null;
export const DataLoaderParams = types.frozen<_DataLoaderParams>();
export const Widget = types.model({
export const Widget = types
.model({
type: types.string,
id: types.string,
title: types.string,
collapsed: types.maybe(types.boolean),
visible: types.boolean,
widgetParams: types.maybe(WidgetParams),
dataLoaderParams: types.maybe(DataLoaderParams),
});
loaded: false,
data: types.maybe(types.frozen<any>()),
})
.actions((self) => {
return {
show: () => {
self.visible = false;
},
hide: () => {
self.visible = true;
},
toggle: () => {
self.visible = !self.visible;
},
setData: (data: any) => {
self.loaded = true;
self.data = data;
},
};
});
export type IWidget = Instance<typeof Widget>;
export function createWidgetStore(
id: string,
type: string,
title: string,
collapsed = false,
widgetParams?: _WidgetParams,
dataLoaderParams?: _DataLoaderParams,
): IWidget {
// eslint-disable-next-line prefer-rest-params
console.debug('init new Widget store, params:', arguments);
return Widget.create({
id: id,
type: type,
title: title,
collapsed: collapsed,
visible: !collapsed,
widgetParams: widgetParams,
dataLoaderParams: dataLoaderParams,
});
}
export const Data = types.model({
widgets: types.array(Widget),
@ -32,17 +76,68 @@ export const Dashboard = types
.actions((self) => {
return {
setData: (data: any) => {
console.debug('Dashboard store new data:', data); // DEBUG
if (data.widgets) {
for (let i = 0; i < data.widgets.length; i++) {
const widget = data.widgets[i];
widget.visible = !widget.collapsed;
}
}
self.data = data;
self.loaded = true;
},
setWidgetsData: (data: any) => {
for (let i = 0; i < data.length; i++) {
const widgetData = data[i]?.data;
const widgetId = data[i]?.widget?.id;
if (!widgetId || !widgetData) continue;
const widgets = self.data?.widgets;
if (!widgets || widgets.length <= 0) return;
const widget = widgets.find((w) => w.id == widgetId);
if (!widget) continue;
widget.setData(widgetData);
}
},
setWidgetData: (widgetId: string, data: any) => {
const widget = self.data?.widgets.find((w) => w.id == widgetId);
if (!widget) return;
widget.setData(data);
},
};
});
export type IDashboard = Instance<typeof Dashboard>;
export async function DashboardLoadData(store: IDashboard): Promise<void> {
console.debug('DashboardLoadData store:', store); // DEBUG
const url = `${process.env.REACT_APP_BACKEND}api/dashboard/${store.id}`;
const resp = await axios.get(url);
console.debug('DashboardLoadData resp:', resp); // DEBUG
if (!resp.data) return;
store.setData(resp.data);
}
export async function LoadDataForWidgets(store: IDashboard): Promise<void> {
const url = `${process.env.REACT_APP_BACKEND}api/dashboard/${store.id}/load-data`;
const resp = await fetch(url);
if (resp && resp.ok) {
const data = await resp.json();
store.setWidgetsData(data);
}
}
export async function LoadDataForWidget(
store: IDashboard,
widgetId: string,
): Promise<void> {
const url = `${process.env.REACT_APP_BACKEND}api/dashboard/${store.id}/load-data/${widgetId}`;
const resp = await fetch(url);
if (resp && resp.ok) {
const data = await resp.json();
store.setWidgetData(widgetId, data);
}
return;
}

View file

@ -1,13 +1,36 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import * as Store from './dashboard-store';
import * as DashboardStoreNs from './dashboard-store';
import * as TopRightMenuNs from '../misc-components/top-right-menu';
import * as WidgetNs from './widget';
import { DebugInfo } from '../misc-components/debug-info';
export type Props = { store: Store.IDashboard };
export type Props = { store: DashboardStoreNs.IDashboard };
export const Dashboard = observer((props: Props): JSX.Element => {
if (!props.store.loaded) {
console.debug('Dashboard - store:', JSON.stringify(props.store)); // DEBUG
return <pre>Loading... {JSON.stringify(props.store)}</pre>;
}
return <pre>{JSON.stringify(props.store, null, ' ')}</pre>;
const debugInfo = JSON.stringify(props.store, null, ' ');
const topRightMenuStore = TopRightMenuNs.Store.create({ visible: false });
const widgets = props.store.data?.widgets.map((widget) => {
return <WidgetNs.Widget key={widget.id} store={widget}></WidgetNs.Widget>;
});
const res = (
<div>
<TopRightMenuNs.TopRightMenu store={topRightMenuStore}>
<a href="/dashboards">Назад</a>
<span>Дашборд - {props.store.data?.title || props.store.id}</span>
</TopRightMenuNs.TopRightMenu>
{widgets}
<DebugInfo value={debugInfo} />
</div>
);
return res;
});

View file

@ -0,0 +1,96 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import * as DashboardStoreNs from './dashboard-store';
import * as WidgetFactoryNs from './widgets/widget-factory';
export type Props = {
store: DashboardStoreNs.IWidget;
};
/**
* Пример данных передаваемых в виджет:
*
* {
* "type": "kanban_by_tree",
* "id": "first",
* "title": "Первый виджет",
* "dataLoaderParams": {
* "rootIssueId": 2,
* "groups": {
* "fromIssues": [
* {
* "issueId": 3,
* "name": "Тест"
* }
* ],
* "fromIssuesIncluded": false,
* "showOthers": true
* },
* "statuses": [
* "New",
* "Re-opened",
* "In Progress",
* "Code Review",
* "Resolved",
* "Testing",
* "Feedback",
* "Wait Release",
* "Pending",
* "Closed",
* "Rejected"
* ],
* "tags": {
* "tagsKeyName": "tags",
* "styledTagsKeyName": "styledTags",
* "styles": {
* "supertag": "background-color: rgb(128, 0, 0); color: rgb(255, 255, 255);"
* },
* "defaultStyle": "background-color: rgb(128, 128, 128);"
* },
* "priorities": [
* {
* "rules": [
* {
* "priorityName": "P1",
* "style": "background-color: #DB2228;"
* },
* {
* "priorityName": "P2",
* "style": "background-color: #FA5E26;"
* },
* {
* "priorityName": "P3",
* "style": "background-color: #FDAF19;"
* },
* {
* "priorityName": "P4",
* "style": "background-color: #31A8FF;",
* "default": true
* },
* {
* "priorityName": "P5",
* "style": "background-color: #FFFFFF; border: 0.5px solid #393838; color: #202020;"
* }
* ],
* "targetKey": "priorityStyle"
* }
* ]
* }
* }
*/
export const Widget = observer((props: Props): JSX.Element => {
const display = props.store.visible ? 'block' : 'none';
return (
<div>
<div>
<button onClick={() => props.store.toggle()}>Show/hide</button>
<span>Title - {props.store.title}</span>
</div>
<div style={{ display: display }}>
<WidgetFactoryNs.WidgetFactory store={props.store} />
</div>
</div>
);
});

View file

@ -0,0 +1,17 @@
import { Instance, onSnapshot } from 'mobx-state-tree';
import * as DashboardStoreNs from '../dashboard-store';
import { observer } from 'mobx-react-lite';
import * as KanbanBoardsNs from '../../kanban-board/kanban-boards';
import * as KanbanBoardsStoreNs from '../../kanban-board/store';
export type Props = {
store: Instance<typeof DashboardStoreNs.Widget>;
};
export const KanbanByTree = observer((props: Props): JSX.Element => {
const store = KanbanBoardsStoreNs.PageStore.create({ loaded: false });
onSnapshot(props.store, (state) => {
store.setData(state.data.data);
});
return <KanbanBoardsNs.KanbanBoards store={store} />;
});

View file

@ -0,0 +1,26 @@
import React from 'react';
import * as DashboardStoreNs from '../dashboard-store';
import { Instance } from 'mobx-state-tree';
import { observer } from 'mobx-react-lite';
import * as KanbanByTreeWidgetNs from './kanban-by-tree';
import { DebugInfo } from '../../misc-components/debug-info';
export type Props = {
store: Instance<typeof DashboardStoreNs.Widget>;
};
export const WidgetFactory = observer((props: Props): JSX.Element => {
const type = props.store.type;
switch (type) {
case 'kanban_by_tree':
return <KanbanByTreeWidgetNs.KanbanByTree store={props.store} />;
default:
return (
<div>
<div>Unknown widget</div>
<DebugInfo value={JSON.stringify(props.store, null, ' ')} />
</div>
);
}
});

View file

@ -0,0 +1,5 @@
.debugInfo {
margin: 3px;
padding: 3px;
border: 1px solid black;
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import Css from './debug-info.module.css';
export type Props = {
value?: string;
children?: any;
};
export const DebugInfo = (props: Props): JSX.Element => {
let output: any;
if (props.value) {
output = <pre>{props.value}</pre>;
} else if (props.children) {
output = props.children;
} else {
output = <pre>(none)</pre>;
}
return <div className={Css.debugInfo}>{output}</div>;
};

View file

@ -36,6 +36,17 @@ export class DashboardController {
);
}
@Get(':id/load-data/:widgetId')
async loadDataForWidget(
@Param('id') id: string,
@Param('widgetId') widgetId: string,
): Promise<any> {
return await getOrAppErrorOrThrow(
() => this.dashboardsDataService.loadDataForWidget(id, widgetId),
BadRequestErrorHandler,
);
}
@Put(':id')
async save(@Param('id') id: string, @Body() data: any): Promise<void> {
const res = await getOrAppErrorOrThrow(

View file

@ -24,6 +24,7 @@ export class DashboardsDataService {
let isSuccess = false;
for (let i = 0; i < cfg.widgets.length; i++) {
const widget = cfg.widgets[i];
if (widget.collapsed) continue;
const loadRes = await this.loadWidgetData(
widget.type,
widget.widgetParams,
@ -33,13 +34,32 @@ export class DashboardsDataService {
if (loadRes.result) {
isSuccess = true;
loadRes.result.widgetId = widget.id;
results.push(loadRes.result);
results.push({ data: loadRes.result, widget: widget });
}
}
if (!isSuccess) throw createAppError('CANNOT_LOAD_DATA');
return results;
}
async loadDataForWidget(
id: string,
widgetId: string,
): Promise<WidgetWithData> {
const cfg = await this.dashboardsService.load(id);
const widget = cfg.widgets.find((widget) => {
return widget.id == widgetId;
});
if (!widget) throw createAppError('WIDGET_NOT_FOUND');
const loadRes = await this.loadWidgetData(
widget.type,
widget.widgetParams,
widget.dataLoaderParams,
cfg,
);
if (loadRes.result) return { widget: widget, data: loadRes.error };
throw createAppError('CANNOT_LOAD_DATA');
}
async loadWidgetData(
type: string,
widgetParams: DashboardModel.WidgetParams,