@boarteam/boar-pack-common-frontend 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boarteam/boar-pack-common-frontend",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "Common frontend package for Boar Pack",
5
5
  "repository": "git@github.com:boarteam/boar-pack.git",
6
6
  "author": "Andrew Balakirev <balakirev.andrey@gmail.com>",
@@ -46,5 +46,5 @@
46
46
  "scripts": {
47
47
  "yalc:push": "yalc push"
48
48
  },
49
- "gitHead": "29f4f6c6598c7422bef8e02dfd1e679b471a4aa4"
49
+ "gitHead": "15bb55f9ca20f3a86c95c9b519e3b31ef1ed1772"
50
50
  }
@@ -1,6 +1,6 @@
1
1
  import { ActionType } from "@ant-design/pro-table";
2
2
  import React, { useEffect, useMemo, useRef, useState } from "react";
3
- import { Button, Result, Tooltip } from "antd";
3
+ import { Button, Result, Tabs, Tooltip } from "antd";
4
4
  import { DeleteOutlined, StopOutlined } from "@ant-design/icons";
5
5
  import { FormattedMessage, useIntl } from "react-intl";
6
6
  import { TDescriptionsProps, TGetOneParams } from "./descriptionTypes";
@@ -12,6 +12,27 @@ import safetyRun from "../../tools/safetyRun";
12
12
  import { buildJoinFields, collectFieldsFromColumns } from "../Table";
13
13
  import { RowEditableConfig } from "@ant-design/pro-utils";
14
14
  import { useForm } from "antd/es/form/Form";
15
+ import { VIEW_MODE_TYPE } from "../Table/ContentViewModeButton";
16
+ import { createStyles } from "antd-style";
17
+
18
+ const useStyles = createStyles(() => {
19
+ return {
20
+ antDescriptionsStyles: {
21
+ '.anticon-edit': {
22
+ opacity: 0,
23
+ transition: 'opacity 200ms'
24
+ },
25
+ '.ant-descriptions-item-content': {
26
+ width: '20%',
27
+ },
28
+ '.ant-descriptions-item-content:hover': {
29
+ '.anticon-edit': {
30
+ opacity: 1
31
+ },
32
+ }
33
+ }
34
+ }
35
+ })
15
36
 
16
37
  const Descriptions = <Entity extends Record<string | symbol, any>,
17
38
  CreateDto = Entity,
@@ -33,12 +54,14 @@ const Descriptions = <Entity extends Record<string | symbol, any>,
33
54
  columns,
34
55
  params,
35
56
  onEntityChange,
57
+ viewMode = VIEW_MODE_TYPE.GENERAL,
36
58
  ...rest
37
59
  }: TDescriptionsProps<Entity,
38
60
  CreateDto,
39
61
  UpdateDto,
40
62
  TPathParams> & Omit<ProDescriptionsProps<Entity>, 'columns'>
41
63
  ) => {
64
+ const { styles } = useStyles();
42
65
  const [form] = useForm<Entity>();
43
66
  const actionRefComponent = useRef<ActionType>();
44
67
  const actionRef = actionRefProp || actionRefComponent;
@@ -65,6 +88,9 @@ const Descriptions = <Entity extends Record<string | symbol, any>,
65
88
  return queryParams;
66
89
  }, [params, pathParams]);
67
90
 
91
+ const getKey = (index: number) =>
92
+ index + String(pathParams?.[idColumnName as keyof TPathParams])
93
+
68
94
  const requestData = async () => {
69
95
  if (!getOne) {
70
96
  return;
@@ -129,36 +155,58 @@ const Descriptions = <Entity extends Record<string | symbol, any>,
129
155
  );
130
156
  }
131
157
 
158
+ const descriptions = sections.map((section, index) => {
159
+ // In the general view mode we need to render extra elements only ones for the top one section
160
+ if (viewMode === VIEW_MODE_TYPE.GENERAL && rest.extra && index !== 0) {
161
+ rest.extra = null;
162
+ }
163
+
164
+ return <ProDescriptions<Entity>
165
+ // @ts-ignore-next-line
166
+ form={form}
167
+ key={getKey(index)}
168
+ title={section.title as React.ReactNode}
169
+ actionRef={actionRef}
170
+ size={"small"}
171
+ bordered
172
+ loading={loading}
173
+ style={{ marginBottom: 20 }}
174
+ labelStyle={{ width: '15%' }}
175
+ dataSource={data as Entity}
176
+ className={styles.antDescriptionsStyles}
177
+ editable={canEdit ? {
178
+ type: 'multiple',
179
+ onSave,
180
+ deletePopconfirmMessage: intl.formatMessage({ id: 'table.deletePopconfirmMessage' }),
181
+ onlyAddOneLineAlertMessage: intl.formatMessage({ id: 'table.onlyAddOneLineAlertMessage' }),
182
+ cancelText: <Tooltip title={intl.formatMessage({ id: 'table.cancelText' })}><StopOutlined /></Tooltip>,
183
+ deleteText: <Tooltip title={intl.formatMessage({ id: 'table.deleteText' })}><DeleteOutlined /></Tooltip>,
184
+ saveText: <Button size={"small"} type={"primary"}><FormattedMessage id={'table.saveText'} /></Button>,
185
+ ...editable,
186
+ }: undefined}
187
+ columns={section.columns}
188
+ {...rest}
189
+ />
190
+ })
191
+
132
192
  return (
133
193
  <>
134
- {sections.map((section, index) => (
135
- <ProDescriptions<Entity>
136
- // @ts-ignore-next-line
137
- form={form}
138
- key={index + String(pathParams?.[idColumnName as keyof TPathParams])}
139
- title={section.title as React.ReactNode}
140
- actionRef={actionRef}
141
- size={"small"}
142
- bordered
143
- loading={loading}
144
- style={{ marginBottom: 20 }}
145
- labelStyle={{ width: '15%' }}
146
- contentStyle={{ width: '20%' }}
147
- dataSource={data as Entity}
148
- editable={canEdit ? {
149
- type: 'multiple',
150
- onSave,
151
- deletePopconfirmMessage: intl.formatMessage({ id: 'table.deletePopconfirmMessage' }),
152
- onlyAddOneLineAlertMessage: intl.formatMessage({ id: 'table.onlyAddOneLineAlertMessage' }),
153
- cancelText: <Tooltip title={intl.formatMessage({ id: 'table.cancelText' })}><StopOutlined /></Tooltip>,
154
- deleteText: <Tooltip title={intl.formatMessage({ id: 'table.deleteText' })}><DeleteOutlined /></Tooltip>,
155
- saveText: <Button size={"small"} type={"primary"}><FormattedMessage id={'table.saveText'} /></Button>,
156
- ...editable,
157
- }: undefined}
158
- columns={section.columns}
159
- {...rest}
160
- />
161
- ))}
194
+ {
195
+ viewMode === VIEW_MODE_TYPE.TABS ?
196
+ (<Tabs defaultActiveKey="0">
197
+ {
198
+ descriptions.map((description, index) => (
199
+ <Tabs.TabPane
200
+ tab={description.props.title as React.ReactNode}
201
+ key={getKey(index)}
202
+ >
203
+ {description}
204
+ </Tabs.TabPane>
205
+ ))
206
+ }
207
+ </Tabs>)
208
+ : descriptions
209
+ }
162
210
  </>
163
211
  );
164
212
  };
@@ -1,10 +1,11 @@
1
- import { useEffect, useMemo } from "react";
2
- import { Button, Modal } from "antd";
1
+ import React, { useEffect, useMemo } from "react";
2
+ import { Button, Modal, Tabs } from "antd";
3
3
  import { TDescriptionsCreateModalProps } from "./descriptionTypes";
4
4
  import { ProDescriptions } from "@ant-design/pro-components";
5
5
  import { columnsToDescriptionItemProps } from "./useDescriptionColumns";
6
6
  import { useForm } from "antd/es/form/Form";
7
7
  import { buildFieldsFromColumnsForDescriptionsDisplay } from "../Table";
8
+ import { VIEW_MODE_TYPE } from "../Table/ContentViewModeButton";
8
9
 
9
10
  const DescriptionsCreateModal = <Entity extends Record<string | symbol, any>>({
10
11
  idColumnName,
@@ -12,11 +13,15 @@ const DescriptionsCreateModal = <Entity extends Record<string | symbol, any>>({
12
13
  data,
13
14
  onClose,
14
15
  onSubmit,
16
+ viewMode = VIEW_MODE_TYPE.GENERAL,
15
17
  ...rest
16
18
  }: TDescriptionsCreateModalProps<Entity>) => {
17
19
  const sections = columnsToDescriptionItemProps(columns, 'General');
18
20
  const [form] = useForm();
19
21
 
22
+ const getKey = (index: number) =>
23
+ index + (Array.isArray(idColumnName) ? idColumnName.join('-') : idColumnName)
24
+
20
25
  const editableKeys = useMemo(() => {
21
26
  return [
22
27
  ...buildFieldsFromColumnsForDescriptionsDisplay(
@@ -30,6 +35,31 @@ const DescriptionsCreateModal = <Entity extends Record<string | symbol, any>>({
30
35
  data ? form.setFieldsValue(data) : form.resetFields();
31
36
  }, [data]);
32
37
 
38
+ const descriptions = sections.map((section, index) => {
39
+ // In the general view mode we need to render extra elements only ones for the top one section
40
+ if (viewMode === VIEW_MODE_TYPE.GENERAL && rest.extra && index !== 0) {
41
+ rest.extra = null;
42
+ }
43
+
44
+ return <ProDescriptions<Entity>
45
+ key={getKey(index)}
46
+ title={section.title as React.ReactNode}
47
+ size={"small"}
48
+ bordered
49
+ column={2}
50
+ style={{ marginBottom: 20 }}
51
+ labelStyle={{ width: '15%' }}
52
+ contentStyle={{ width: '25%' }}
53
+ editable={{
54
+ form,
55
+ editableKeys,
56
+ actionRender: () => [],
57
+ }}
58
+ columns={section.columns}
59
+ {...rest}
60
+ />
61
+ })
62
+
33
63
  return (
34
64
  <Modal
35
65
  open={data !== undefined}
@@ -38,26 +68,24 @@ const DescriptionsCreateModal = <Entity extends Record<string | symbol, any>>({
38
68
  footer={[
39
69
  <Button key='submit' type="primary" onClick={async () => form.validateFields().then(onSubmit)}>Create</Button>
40
70
  ]}
71
+ closeIcon={false}
41
72
  >
42
- {sections.map((section, index) => (
43
- <ProDescriptions<Entity>
44
- key={index + (Array.isArray(idColumnName) ? idColumnName.join('-') : idColumnName)}
45
- title={section.title as React.ReactNode}
46
- size={"small"}
47
- bordered
48
- column={2}
49
- style={{ marginBottom: 20 }}
50
- labelStyle={{ width: '15%' }}
51
- contentStyle={{ width: '25%' }}
52
- editable={{
53
- form,
54
- editableKeys,
55
- actionRender: () => [],
56
- }}
57
- columns={section.columns}
58
- {...rest}
59
- />
60
- ))}
73
+ {
74
+ viewMode === VIEW_MODE_TYPE.TABS ?
75
+ (<Tabs defaultActiveKey="0">
76
+ {
77
+ descriptions.map((description, index) => (
78
+ <Tabs.TabPane
79
+ tab={description.props.title as React.ReactNode}
80
+ key={getKey(index)}
81
+ >
82
+ {description}
83
+ </Tabs.TabPane>
84
+ ))
85
+ }
86
+ </Tabs>)
87
+ : descriptions
88
+ }
61
89
  </Modal>
62
90
  );
63
91
  };
@@ -2,7 +2,9 @@ import { MutableRefObject } from "react";
2
2
  import { ActionType } from "@ant-design/pro-table";
3
3
  import { RowEditableConfig } from "@ant-design/pro-utils";
4
4
  import { QueryJoin } from "@nestjsx/crud-request";
5
- import { ProColumns, ProDescriptionsProps } from "@ant-design/pro-components";
5
+ import { ProColumns } from "@ant-design/pro-components";
6
+ import { VIEW_MODE_TYPE } from "../Table/ContentViewModeButton";
7
+ import { ProDescriptionsProps } from "@ant-design/pro-descriptions";
6
8
 
7
9
  export type TGetOneParams = {
8
10
  /**
@@ -38,6 +40,7 @@ export type TDescriptionsProps<Entity, CreateDto, UpdateDto, TPathParams = objec
38
40
  params?: TDescriptionGetRequestParams,
39
41
  columns: ProColumns<Entity>[],
40
42
  onEntityChange?: (entity: Entity | null) => void;
43
+ viewMode?: VIEW_MODE_TYPE,
41
44
  }
42
45
 
43
46
  export type TDescriptionsCreateModalProps<Entity> = Omit<ProDescriptionsProps<Entity>, 'columns'> & {
@@ -46,4 +49,5 @@ export type TDescriptionsCreateModalProps<Entity> = Omit<ProDescriptionsProps<En
46
49
  data: Partial<Entity> | undefined,
47
50
  onSubmit: (data: Entity) => Promise<void>,
48
51
  onClose: () => void,
52
+ viewMode?: VIEW_MODE_TYPE,
49
53
  }
@@ -0,0 +1,27 @@
1
+ import { Button, Tooltip } from "antd";
2
+ import { AppstoreOutlined, UnorderedListOutlined } from "@ant-design/icons";
3
+ export enum VIEW_MODE_TYPE {
4
+ TABS = 'tabs',
5
+ GENERAL = 'general'
6
+ }
7
+
8
+ interface ContentViewModeButtonProps {
9
+ contentViewMode: VIEW_MODE_TYPE;
10
+ setContentViewMode: (mode: VIEW_MODE_TYPE) => void;
11
+ }
12
+
13
+ const ContentViewModeButton: React.FC<ContentViewModeButtonProps> = ({ contentViewMode, setContentViewMode }) => {
14
+ return (
15
+ <Tooltip
16
+ title={contentViewMode === VIEW_MODE_TYPE.TABS ? 'Switch to general view' : 'Switch to tabs view'}
17
+ key="viewModeToggle">
18
+ <Button
19
+ type="text"
20
+ icon={contentViewMode === VIEW_MODE_TYPE.TABS ? <UnorderedListOutlined/> :
21
+ <AppstoreOutlined/>}
22
+ onClick={() => setContentViewMode(contentViewMode === VIEW_MODE_TYPE.TABS ? VIEW_MODE_TYPE.GENERAL : VIEW_MODE_TYPE.TABS)}
23
+ />
24
+ </Tooltip>);
25
+ };
26
+
27
+ export default ContentViewModeButton;
@@ -1,17 +1,24 @@
1
1
  import ProTable, { ActionType } from "@ant-design/pro-table";
2
2
  import React, { useEffect, useRef, useState } from "react";
3
- import { Button, Popover, Space, Tooltip, message } from "antd";
4
- import { DeleteOutlined, PlusOutlined, QuestionCircleTwoTone, StopOutlined } from "@ant-design/icons";
3
+ import {Button, Popover, Space, Tooltip, message, Modal } from "antd";
4
+ import {
5
+ DeleteOutlined,
6
+ PlusOutlined,
7
+ QuestionCircleTwoTone,
8
+ StopOutlined,
9
+ } from "@ant-design/icons";
5
10
  import { FormattedMessage, useIntl } from "react-intl";
6
11
  import { flushSync } from "react-dom";
7
12
  import { applyKeywordToSearch, buildJoinFields, collectFieldsFromColumns, getFiltersSearch } from "./tableTools";
8
13
  import { TFilterParams, TFilters, TGetAllParams, TSort, TTableProps } from "./tableTypes";
9
14
  import useColumnsSets from "./useColumnsSets";
10
- import DescriptionsCreateModal from "../Descriptions/DescriptionsCreateModal";
11
15
  import BulkEditButton from "./BulkEditButton";
12
16
  import _ from "lodash";
13
17
  import BulkDeleteButton from "./BulkDeleteButton";
14
18
  import { createStyles } from "antd-style";
19
+ import ContentViewModeButton, { VIEW_MODE_TYPE } from "./ContentViewModeButton";
20
+ import { Descriptions } from "../Descriptions";
21
+ import DescriptionsCreateModal from "../Descriptions/DescriptionsCreateModal";
15
22
 
16
23
  let creatingRecordsCount = 0;
17
24
 
@@ -55,6 +62,7 @@ const Table = <Entity extends Record<string | symbol, any>,
55
62
  entityToCreateDto,
56
63
  entityToUpdateDto,
57
64
  createNewDefaultParams,
65
+ editableRecord,
58
66
  afterSave,
59
67
  actionRef: actionRefProp,
60
68
  editable,
@@ -69,6 +77,7 @@ const Table = <Entity extends Record<string | symbol, any>,
69
77
  toolBarRender,
70
78
  params,
71
79
  popupDataState,
80
+ editPopupTitle,
72
81
  ...rest
73
82
  }: TTableProps<Entity,
74
83
  CreateDto,
@@ -79,18 +88,21 @@ const Table = <Entity extends Record<string | symbol, any>,
79
88
  const actionRefComponent = useRef<ActionType>();
80
89
  const actionRef = actionRefProp || actionRefComponent;
81
90
  const [createPopupData, setCreatePopupData] = popupDataState ?? useState<Partial<Entity> | undefined>();
91
+ const [updatePopupData, setUpdatePopupData] = useState<Partial<Entity> | undefined>();
82
92
  const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
83
93
  const [selectedRecords, setSelectedRecords] = useState<Entity[]>([]);
84
94
  const [lastRequest, setLastRequest] = useState<[TGetAllParams & TPathParams, any] | []>([]);
85
95
  const [allSelected, setAllSelected] = useState(false);
86
96
  const { styles } = useStyles();
87
97
  const [messageApi, contextHolder] = message.useMessage();
98
+ const [descriptionsModalViewMode, setDescriptionsModalViewMode] = useState<VIEW_MODE_TYPE>(VIEW_MODE_TYPE.TABS);
88
99
 
89
100
  const intl = useIntl();
90
101
 
91
102
  useEffect(() => {
103
+ setUpdatePopupData(editableRecord);
92
104
  actionRef?.current?.reload();
93
- }, [JSON.stringify(pathParams), JSON.stringify(params)]);
105
+ }, [editableRecord, JSON.stringify(pathParams), JSON.stringify(params)]);
94
106
 
95
107
  const {
96
108
  columnsSetSelect: localColumnsSetSelect,
@@ -264,7 +276,6 @@ const Table = <Entity extends Record<string | symbol, any>,
264
276
  ...editable,
265
277
  }}
266
278
  toolBarRender={(...args) => [
267
- ...toolBarRender && toolBarRender(...args) || [],
268
279
  columnsSetSelect?.() || null,
269
280
  !viewOnly && onUpdateMany
270
281
  ? (
@@ -323,6 +334,7 @@ const Table = <Entity extends Record<string | symbol, any>,
323
334
  )
324
335
  : <></>,
325
336
  !viewOnly && createButton || null,
337
+ ...toolBarRender && toolBarRender(...args) || [],
326
338
  ]}
327
339
  columns={columns}
328
340
  defaultSize='small'
@@ -392,7 +404,36 @@ const Table = <Entity extends Record<string | symbol, any>,
392
404
  }}
393
405
  idColumnName={idColumnName}
394
406
  columns={columns ?? []}
407
+ viewMode={descriptionsModalViewMode}
408
+ extra={
409
+ <ContentViewModeButton
410
+ contentViewMode={descriptionsModalViewMode}
411
+ setContentViewMode={setDescriptionsModalViewMode}
412
+ />
413
+ }
395
414
  />
415
+ <Modal
416
+ title={editPopupTitle}
417
+ open={updatePopupData !== undefined}
418
+ width='80%'
419
+ footer={null}
420
+ closeIcon={false}
421
+ onCancel={() => setUpdatePopupData(undefined)}
422
+ >
423
+ <Descriptions<Entity>
424
+ mainTitle='Main'
425
+ columns={columns}
426
+ canEdit={true}
427
+ entity={updatePopupData}
428
+ viewMode={descriptionsModalViewMode}
429
+ extra={
430
+ <ContentViewModeButton
431
+ contentViewMode={descriptionsModalViewMode}
432
+ setContentViewMode={setDescriptionsModalViewMode}
433
+ />
434
+ }
435
+ />
436
+ </Modal>
396
437
  {contextHolder}
397
438
  </>);
398
439
  };
@@ -88,6 +88,7 @@ interface BaseProps<Entity,
88
88
  pathParams: TPathParams;
89
89
  idColumnName?: string & keyof Entity | (string & keyof Entity)[];
90
90
  createNewDefaultParams?: Partial<Entity>;
91
+ editableRecord?: Partial<Entity>;
91
92
  afterSave?: (record: Entity) => Promise<void>;
92
93
  actionRef?: MutableRefObject<ActionType | undefined>;
93
94
  editable?: RowEditableConfig<Entity>;
@@ -99,6 +100,7 @@ interface BaseProps<Entity,
99
100
  columnsState?: ColumnStateType;
100
101
  columnsSetSelect?: () => React.ReactNode;
101
102
  popupDataState?: [Partial<Entity>, React.Dispatch<React.SetStateAction<Partial<Entity>>>]
103
+ editPopupTitle?: string;
102
104
  }
103
105
 
104
106
  interface EditableProps<Entity, CreateDto, UpdateDto, TPathParams = {}> {