diff --git a/frontend/src/dashboard/dashboard-page.tsx b/frontend/src/dashboard/dashboard-page.tsx index a256449..4e77823 100644 --- a/frontend/src/dashboard/dashboard-page.tsx +++ b/frontend/src/dashboard/dashboard-page.tsx @@ -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 ; }; diff --git a/frontend/src/dashboard/dashboard-store.tsx b/frontend/src/dashboard/dashboard-store.tsx index 78cca96..dffcc2d 100644 --- a/frontend/src/dashboard/dashboard-store.tsx +++ b/frontend/src/dashboard/dashboard-store.tsx @@ -9,14 +9,58 @@ type _DataLoaderParams = Record | null; export const DataLoaderParams = types.frozen<_DataLoaderParams>(); -export const Widget = types.model({ - type: types.string, - id: types.string, - title: types.string, - collapsed: types.maybe(types.boolean), - widgetParams: types.maybe(WidgetParams), - dataLoaderParams: types.maybe(DataLoaderParams), -}); +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()), + }) + .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; + +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; export async function DashboardLoadData(store: IDashboard): Promise { + 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 { + 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 { + 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; +} diff --git a/frontend/src/dashboard/dashboard.tsx b/frontend/src/dashboard/dashboard.tsx index 7dfd4e2..47ef169 100644 --- a/frontend/src/dashboard/dashboard.tsx +++ b/frontend/src/dashboard/dashboard.tsx @@ -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
Loading... {JSON.stringify(props.store)}
; } - return
{JSON.stringify(props.store, null, '  ')}
; + const debugInfo = JSON.stringify(props.store, null, ' '); + + const topRightMenuStore = TopRightMenuNs.Store.create({ visible: false }); + + const widgets = props.store.data?.widgets.map((widget) => { + return ; + }); + + const res = ( +
+ + Назад + Дашборд - {props.store.data?.title || props.store.id} + + {widgets} + +
+ ); + + return res; }); diff --git a/frontend/src/dashboard/widget.tsx b/frontend/src/dashboard/widget.tsx new file mode 100644 index 0000000..0008e55 --- /dev/null +++ b/frontend/src/dashboard/widget.tsx @@ -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 ( +
+
+ + Title - {props.store.title} +
+
+ +
+
+ ); +}); diff --git a/frontend/src/dashboard/widgets/kanban-by-tree.tsx b/frontend/src/dashboard/widgets/kanban-by-tree.tsx new file mode 100644 index 0000000..8641284 --- /dev/null +++ b/frontend/src/dashboard/widgets/kanban-by-tree.tsx @@ -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; +}; + +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 ; +}); diff --git a/frontend/src/dashboard/widgets/widget-factory.tsx b/frontend/src/dashboard/widgets/widget-factory.tsx new file mode 100644 index 0000000..1cb27fb --- /dev/null +++ b/frontend/src/dashboard/widgets/widget-factory.tsx @@ -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; +}; + +export const WidgetFactory = observer((props: Props): JSX.Element => { + const type = props.store.type; + + switch (type) { + case 'kanban_by_tree': + return ; + default: + return ( +
+
Unknown widget
+ +
+ ); + } +}); diff --git a/frontend/src/misc-components/debug-info.module.css b/frontend/src/misc-components/debug-info.module.css new file mode 100644 index 0000000..f429782 --- /dev/null +++ b/frontend/src/misc-components/debug-info.module.css @@ -0,0 +1,5 @@ +.debugInfo { + margin: 3px; + padding: 3px; + border: 1px solid black; +} \ No newline at end of file diff --git a/frontend/src/misc-components/debug-info.tsx b/frontend/src/misc-components/debug-info.tsx new file mode 100644 index 0000000..de71c46 --- /dev/null +++ b/frontend/src/misc-components/debug-info.tsx @@ -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 =
{props.value}
; + } else if (props.children) { + output = props.children; + } else { + output =
(none)
; + } + return
{output}
; +}; diff --git a/libs/event-emitter/src/dashboards/dashboard.controller.ts b/libs/event-emitter/src/dashboards/dashboard.controller.ts index 013f769..980c234 100644 --- a/libs/event-emitter/src/dashboards/dashboard.controller.ts +++ b/libs/event-emitter/src/dashboards/dashboard.controller.ts @@ -36,6 +36,17 @@ export class DashboardController { ); } + @Get(':id/load-data/:widgetId') + async loadDataForWidget( + @Param('id') id: string, + @Param('widgetId') widgetId: string, + ): Promise { + return await getOrAppErrorOrThrow( + () => this.dashboardsDataService.loadDataForWidget(id, widgetId), + BadRequestErrorHandler, + ); + } + @Put(':id') async save(@Param('id') id: string, @Body() data: any): Promise { const res = await getOrAppErrorOrThrow( diff --git a/libs/event-emitter/src/dashboards/dashboards-data.service.ts b/libs/event-emitter/src/dashboards/dashboards-data.service.ts index 74fe340..484be8b 100644 --- a/libs/event-emitter/src/dashboards/dashboards-data.service.ts +++ b/libs/event-emitter/src/dashboards/dashboards-data.service.ts @@ -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 { + 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,