@boarteam/boar-pack-common-frontend 4.0.0 → 4.1.0

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.
Files changed (60) hide show
  1. package/dist/tools/safetyRun.d.ts +2 -1
  2. package/dist/tools/safetyRun.js +2 -1
  3. package/dist/tools/safetyRun.js.map +1 -1
  4. package/package.json +2 -3
  5. package/src/components/ChangesModal/ChangesModal.tsx +0 -260
  6. package/src/components/ChangesModal/ChangesTab.tsx +0 -51
  7. package/src/components/ChangesModal/ConflictsTab.tsx +0 -176
  8. package/src/components/ChangesModal/ErrorsTab.tsx +0 -49
  9. package/src/components/ChangesModal/NewRecordsTab.tsx +0 -48
  10. package/src/components/ChangesModal/ResultsTab.tsx +0 -28
  11. package/src/components/ChangesModal/index.ts +0 -1
  12. package/src/components/Comment/Comment.tsx +0 -53
  13. package/src/components/Comment/CommentAvatar.tsx +0 -34
  14. package/src/components/Comment/CommentForm.tsx +0 -36
  15. package/src/components/Comment/CommentFormModal.tsx +0 -31
  16. package/src/components/Comment/index.ts +0 -5
  17. package/src/components/Descriptions/Descriptions.tsx +0 -328
  18. package/src/components/Descriptions/DescriptionsCreateModal.tsx +0 -65
  19. package/src/components/Descriptions/descriptionTypes.ts +0 -73
  20. package/src/components/Descriptions/index.ts +0 -5
  21. package/src/components/Descriptions/useContentViewMode.tsx +0 -28
  22. package/src/components/Descriptions/useDescriptionColumns.ts +0 -42
  23. package/src/components/Inputs/DateRange.tsx +0 -75
  24. package/src/components/Inputs/MultiStringSelect.tsx +0 -20
  25. package/src/components/Inputs/NumberInputHandlingNewRecord.tsx +0 -11
  26. package/src/components/Inputs/NumberSwitcher.tsx +0 -27
  27. package/src/components/Inputs/Password.tsx +0 -33
  28. package/src/components/Inputs/RelationSelect.tsx +0 -108
  29. package/src/components/Inputs/SearchSelect.tsx +0 -93
  30. package/src/components/Inputs/filterDropdowns.tsx +0 -144
  31. package/src/components/Inputs/index.ts +0 -9
  32. package/src/components/Inputs/useCheckConnection.tsx +0 -79
  33. package/src/components/List/List.tsx +0 -266
  34. package/src/components/List/index.ts +0 -3
  35. package/src/components/List/listTypes.ts +0 -31
  36. package/src/components/QuestionMarkHint/QuestionMarkHint.tsx +0 -33
  37. package/src/components/QuestionMarkHint/index.ts +0 -1
  38. package/src/components/Table/BulkDeleteButton.tsx +0 -55
  39. package/src/components/Table/BulkEditButton.tsx +0 -160
  40. package/src/components/Table/CreateEntityModal.tsx +0 -85
  41. package/src/components/Table/DeleteButton.tsx +0 -44
  42. package/src/components/Table/Table.tsx +0 -294
  43. package/src/components/Table/getTableDataQueryParams.ts +0 -79
  44. package/src/components/Table/index.ts +0 -7
  45. package/src/components/Table/tableTools.ts +0 -244
  46. package/src/components/Table/tableTypes.ts +0 -148
  47. package/src/components/Table/useBulkEditing.tsx +0 -128
  48. package/src/components/Table/useColumnsSets.tsx +0 -142
  49. package/src/components/Table/useCreation.tsx +0 -114
  50. package/src/components/Table/useEditableTable.tsx +0 -86
  51. package/src/components/Table/useImportExport.tsx +0 -315
  52. package/src/components/index.ts +0 -6
  53. package/src/index.ts +0 -2
  54. package/src/tools/ApiError.ts +0 -18
  55. package/src/tools/WebsocketClient.ts +0 -146
  56. package/src/tools/index.ts +0 -6
  57. package/src/tools/numberTools.ts +0 -6
  58. package/src/tools/safetyRun.ts +0 -5
  59. package/src/tools/useFullscreen.tsx +0 -62
  60. package/src/tools/useTabs.ts +0 -17
@@ -1,315 +0,0 @@
1
- import { Button, Tooltip } from 'antd';
2
- import { DownloadOutlined, UploadOutlined } from "@ant-design/icons";
3
- import { ChangeEvent, useCallback, useRef, useState } from "react";
4
- import { TGetAllParams } from "./tableTypes";
5
- import { Link } from "react-router-dom";
6
- import * as XLSX from "xlsx";
7
- import diff from "deep-diff";
8
- import { ProColumns } from "@ant-design/pro-components";
9
- import { keyBy } from "lodash";
10
- import { TRelationalFields } from "../ChangesModal";
11
-
12
- type TImportedJSON<Entity> = (Entity & { id: string })[]
13
-
14
- export type TUpdatedDiffResult = {
15
- id: string | number,
16
- diff: diff.Diff<any, any>[],
17
- [key: string]: any,
18
- }
19
-
20
- type TUpdatedResult = ({
21
- id: string | number,
22
- version: string | number,
23
- [key: string]: any,
24
- })
25
-
26
- export type TDiffResult<Entity> = {
27
- created: Entity[],
28
- updated: TUpdatedResult[],
29
- tableData: TUpdatedDiffResult[],
30
- }
31
-
32
- export function useImportExport<Entity, TPathParams = {}>({
33
- exportUrl,
34
- exportParams,
35
- columns,
36
- changedRecordsColumnsConfig,
37
- relationalFields,
38
- }: {
39
- exportUrl?: string;
40
- exportParams?: {
41
- [key: string]: string | number
42
- },
43
- columns: ProColumns<Entity>[],
44
- changedRecordsColumnsConfig?: ProColumns<Entity>[],
45
- relationalFields?: TRelationalFields,
46
- }) {
47
- const [isLoadingImport, setIsLoadingImport] = useState(false);
48
- const [lastQueryParams, setLastQueryParams] = useState<TGetAllParams & TPathParams>();
49
- const keyedColumns = keyBy(columns, 'dataIndex');
50
-
51
- const [diffResult, setDiffResult] = useState<TDiffResult<Entity>>();
52
- const fileInputRef = useRef<HTMLInputElement | null>(null);
53
-
54
- const openFileDialog = useCallback(() => {
55
- if (fileInputRef.current) {
56
- fileInputRef.current.value = '';
57
- fileInputRef.current.click();
58
- }
59
- }, []);
60
-
61
- const trueBooleanValues = [true, "TRUE", "True", "true", "1", "yes", "on"];
62
-
63
- const buildExportUrl = useCallback(() => {
64
- const params = {
65
- ...(lastQueryParams && {
66
- s: (lastQueryParams as any).s,
67
- sort: (lastQueryParams as any).sort?.[0],
68
- }),
69
- ...exportParams,
70
- } as Record<string, string | number | undefined>;
71
-
72
- const qp = Object.entries(params)
73
- .filter(([, v]) => v !== undefined && v !== null && v !== '')
74
- .reduce((acc, [k, v]) => {
75
- acc.append(k, String(v));
76
- return acc;
77
- }, new URLSearchParams());
78
-
79
- return exportUrl
80
- ? exportUrl + (qp.toString() ? `?${qp.toString()}` : '')
81
- : undefined;
82
- }, [exportUrl, exportParams, lastQueryParams]);
83
-
84
- const fetchExportCsvArrayBuffer = useCallback(async (): Promise<ArrayBuffer> => {
85
- const url = buildExportUrl();
86
- if (!url) throw new Error('Specify exportUrl!');
87
-
88
- const res = await fetch(url, {
89
- method: 'GET',
90
- });
91
-
92
- if (!res.ok) {
93
- throw new Error(`Can't get actual CSV data for comparing. HTTP ${res.status}`);
94
- }
95
- return await res.arrayBuffer();
96
- }, [buildExportUrl]);
97
-
98
- const normalizeRow = (row: any) => {
99
- const normalizedRow = { ...row };
100
- for (const key in normalizedRow) {
101
- if (normalizedRow[key] === null) {
102
- continue;
103
- }
104
-
105
- // Relational fields handling
106
- const relationalKey = key.replace('_id', '');
107
- if (relationalFields?.has(relationalKey)) {
108
- const relation = relationalFields.get(relationalKey);
109
- const id = Number(normalizedRow[key]);
110
- normalizedRow[relationalKey] = relation.data[id] || null;
111
- continue;
112
- }
113
-
114
- // Boolean values handling
115
- if (keyedColumns[key]?.valueType === "switch") {
116
- normalizedRow[key] = trueBooleanValues.includes(normalizedRow[key]);
117
- continue;
118
- }
119
-
120
- // Numeric values handling
121
- if (keyedColumns[key]?.valueType === "digit") {
122
- normalizedRow[key] = Number(normalizedRow[key]);
123
- continue;
124
- }
125
-
126
- // Empty values handling
127
- if (['', 'null'].includes(normalizedRow[key])) {
128
- normalizedRow[key] = null;
129
- continue;
130
- }
131
-
132
- // Text values handling
133
- // Text by default if not specified
134
- if (keyedColumns[key] && keyedColumns[key].valueType === undefined || 'text') {
135
- normalizedRow[key] = String(normalizedRow[key]);
136
- }
137
- }
138
-
139
- return normalizedRow;
140
- }
141
-
142
- const getRelationalValue = (relationField: {
143
- id: any;
144
- name?: string;
145
- description?: string;
146
- }) => {
147
- return relationField?.name || relationField?.description || relationField?.id;
148
- }
149
-
150
- const handleFileAsync = async (e: ChangeEvent<HTMLInputElement>) => {
151
- setIsLoadingImport(true);
152
-
153
- const fileAfter = e.target.files[0];
154
-
155
- if (!fileAfter) {
156
- throw new Error('Choose CSV with changes.');
157
- }
158
-
159
- console.time('fetch actual export from api');
160
-
161
- // File before now should be fetched from the same endpoint as the export button use
162
- const dataBefore = await fetchExportCsvArrayBuffer();
163
-
164
- console.timeEnd('fetch actual export from api');
165
-
166
- const dataAfter = await fileAfter.arrayBuffer();
167
-
168
- // Data is an ArrayBuffer
169
- const workbookBefore = XLSX.read(dataBefore);
170
- const workbookAfter = XLSX.read(dataAfter, {
171
- type: 'array',
172
- raw: true,
173
- });
174
-
175
- const jsonBefore: TImportedJSON<Entity> = XLSX.utils.sheet_to_json(workbookBefore.Sheets[workbookBefore.SheetNames[0]], {
176
- defval: null
177
- });
178
-
179
- const jsonAfter: TImportedJSON<Entity> = XLSX.utils.sheet_to_json(workbookAfter.Sheets[workbookAfter.SheetNames[0]], {
180
- defval: null
181
- });
182
-
183
- // TODO: Check JSON structure
184
- // ...
185
-
186
- const oldMap = Object.fromEntries(
187
- jsonBefore.map(
188
- (row) => {
189
- return [row.id, normalizeRow(row)]
190
- }
191
- )
192
- );
193
- const newMap: { [key: string]: any } = {};
194
-
195
- const diffResult: TDiffResult<Entity> = {
196
- created: [],
197
- updated: [],
198
- tableData: [],
199
- };
200
-
201
- jsonAfter.map((row) => {
202
- const normalizedRow = normalizeRow(row);
203
-
204
- // New record
205
- if (!normalizedRow.id) {
206
- diffResult.created.push(normalizedRow);
207
- return;
208
- }
209
-
210
- // Existing record
211
- newMap[normalizedRow.id] = normalizedRow;
212
- });
213
-
214
- // Recognize added and changed records
215
- for (const id in newMap) {
216
- if (!oldMap[id]) {
217
- continue;
218
- }
219
-
220
- const differences = diff<any, any>(oldMap[id], newMap[id], {
221
- normalize: (currentPath, key, lhs, rhs) => {
222
- // We don't need to compare versions
223
- if (key === 'version') {
224
- return [true, true];
225
- }
226
-
227
- // We don't care about relational ids. Skip them
228
- if (key.endsWith('_id')) {
229
- return [true, true];
230
- }
231
-
232
- // If the key is a relational field (dictionary value), we need to compare only value fields
233
- if (relationalFields && relationalFields.has(key)) {
234
- return [
235
- getRelationalValue(lhs),
236
- getRelationalValue(rhs),
237
- ]
238
- }
239
-
240
- return [lhs, rhs];
241
- }
242
- });
243
-
244
- if (differences) {
245
- // console.log(differences);
246
- const changedFields = differences.map((diff) => diff.path?.[0]).filter((field: string | undefined) => field);
247
- const displayFields = changedRecordsColumnsConfig.map(column => column.dataIndex);
248
-
249
- const payload: TUpdatedResult = {
250
- id,
251
- version: newMap[id].version,
252
- };
253
-
254
- const tableData: TUpdatedDiffResult = {
255
- id,
256
- diff: differences,
257
- };
258
-
259
- columns.forEach((column) => {
260
- const key = String(column.dataIndex);
261
- const value = newMap[id][key] === undefined ? newMap[id][key + "_id"] : newMap[id][key];
262
- if (changedFields.includes(key)) {
263
- payload[key] = value;
264
- }
265
-
266
- if (displayFields.includes(key)) {
267
- tableData[key] = value;
268
- }
269
- })
270
-
271
- // Will be sent to the server
272
- diffResult.updated.push(payload);
273
-
274
- // Will be shown in the "changed values" tab
275
- diffResult.tableData.push(tableData);
276
- }
277
- }
278
-
279
- // Store this value in the state
280
- setDiffResult(diffResult);
281
- setIsLoadingImport(false);
282
- };
283
-
284
- const exportButton = <Tooltip title="Export">
285
- <Link to={buildExportUrl() ?? '#'} target={'_blank'}>
286
- <Button icon={<UploadOutlined />} />
287
- </Link>
288
- </Tooltip>;
289
-
290
- const importButton = <>
291
- <Tooltip title="Import changes CSV file">
292
- <Button
293
- loading={isLoadingImport}
294
- icon={<DownloadOutlined />}
295
- onClick={openFileDialog}
296
- />
297
- </Tooltip>
298
-
299
- <input
300
- type="file"
301
- ref={fileInputRef}
302
- style={{ display: 'none' }}
303
- accept=".csv"
304
- onChange={handleFileAsync}
305
- />
306
- </>
307
-
308
- return {
309
- exportButton,
310
- importButton,
311
- setLastQueryParams,
312
- diffResult,
313
- setDiffResult,
314
- };
315
- }
@@ -1,6 +0,0 @@
1
- export * from './Descriptions';
2
- export * from './Inputs';
3
- export * from './QuestionMarkHint';
4
- export * from './Table';
5
- export * from './List';
6
- export * from './ChangesModal';
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './components/index';
2
- export * from './tools/index';
@@ -1,18 +0,0 @@
1
- // Should be synced with common-backend/src/tools/ApiError.ts
2
- type TApiErrorBodyType = {
3
- statusCode: number
4
- message: string
5
- errors: {
6
- field: string,
7
- message: string
8
- }[]
9
- }
10
-
11
- // Copied from api-client/generated/core/ApiError.ts
12
- export class ApiError extends Error {
13
- public readonly url: string;
14
- public readonly status: number;
15
- public readonly statusText: string;
16
- public readonly body: TApiErrorBodyType;
17
- public readonly request: any;
18
- }
@@ -1,146 +0,0 @@
1
- import { message } from "antd";
2
-
3
- export type TIncomeEvent = {
4
- event: string;
5
- data: {
6
- message: string;
7
- status?: WebSocket['readyState'];
8
- [key: string]: any;
9
- };
10
- };
11
-
12
- export enum WsErrorCodes {
13
- ConnectionClosed = 1000,
14
- InvalidJson = 4000,
15
- ErrorMessage = 4001,
16
- Unauthorized = 4003,
17
- }
18
-
19
- export class WebsocketClient {
20
- private socket: WebSocket | null;
21
- private reconnectTimeout: number | undefined;
22
- private readonly worker: null | string;
23
- private serverSocketStatus: WebSocket['readyState'] = WebSocket.CLOSED;
24
-
25
- private readonly closeHandler: (event: CloseEvent) => void;
26
- private readonly openHandler: () => void;
27
- private readonly messageHandler: (msg: TIncomeEvent) => void;
28
-
29
- constructor({
30
- worker,
31
- onOpen,
32
- onMessage,
33
- onClose,
34
- }: {
35
- worker: null | string;
36
- onOpen: () => void;
37
- onMessage: (msg: TIncomeEvent) => void;
38
- onClose?: (event: CloseEvent) => void;
39
- }) {
40
- this.worker = worker;
41
- this.closeHandler = onClose || (() => {});
42
- this.openHandler = onOpen;
43
- this.messageHandler = onMessage;
44
-
45
- this.connect();
46
- }
47
-
48
- get status() {
49
- return this.serverSocketStatus === WebSocket.OPEN ? this.socket?.readyState : this.serverSocketStatus;
50
- }
51
-
52
- private connect() {
53
- const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
54
- const url = `${wsProtocol}//${location.host}/ws/${this.worker || 'primary'}/ws`;
55
- console.log(`QuotesDataSocket: connecting to ${url}...`);
56
- this.serverSocketStatus = WebSocket.CONNECTING;
57
- this.socket = new WebSocket(url);
58
- this.socket.addEventListener("open", this.onOpen);
59
- this.socket.addEventListener("message", this.onMessage);
60
- this.socket.addEventListener("error", this.onError);
61
- this.socket.addEventListener("close", this.onClose);
62
- }
63
-
64
- private onError = (event: WebSocketEventMap['error']) => {
65
- console.error(`QuotesDataSocket: error for ${this.worker || 'primary'} socket:`);
66
- console.error(event);
67
- this.socket?.close();
68
- }
69
-
70
- private onClose = (event: WebSocketEventMap['close']) => {
71
- console.log(`QuotesDataSocket: ${this.worker || 'primary'} socket closed`);
72
- this.socket?.removeEventListener("close", this.onClose);
73
- this.socket?.removeEventListener("error", this.onError);
74
- this.socket?.removeEventListener("message", this.onMessage);
75
- this.socket?.removeEventListener("open", this.onOpen);
76
- this.socket = null;
77
- this.closeHandler(event);
78
- }
79
-
80
- private onOpen = () => {
81
- this.openHandler();
82
- }
83
-
84
- private onMessage = async (event: WebSocketEventMap['message']) => {
85
- let msg: TIncomeEvent;
86
-
87
- try {
88
- msg = JSON.parse(event.data);
89
- } catch (e) {
90
- console.error(`Error, while parsing message from WS server ${event.data}`);
91
- console.error(e);
92
- this.socket?.close(WsErrorCodes.InvalidJson, "Invalid JSON");
93
- return;
94
- }
95
-
96
- if (msg.event === "error") {
97
- console.error(`Error from WS server: ${msg.data.message}`);
98
- this.socket?.close(WsErrorCodes.ErrorMessage, msg.data.message);
99
- await message.error(`WS server error: ${msg.data.message}`);
100
- return;
101
- }
102
-
103
- if (msg.event === "status") {
104
- this.serverSocketStatus = msg.data?.status ?? this.serverSocketStatus;
105
- }
106
-
107
- this.messageHandler(msg);
108
- }
109
-
110
- public close(): Promise<void> {
111
- clearTimeout(this.reconnectTimeout);
112
- if (!this.socket || this.socket.readyState === WebSocket.CLOSED) {
113
- return Promise.resolve();
114
- }
115
-
116
- return new Promise((resolve) => {
117
- this.socket?.addEventListener("close", () => resolve(), { once: true });
118
- this.socket?.close();
119
- });
120
- }
121
-
122
- public reconnect(timeout: number) {
123
- this.reconnectTimeout = setTimeout(() => {
124
- this.connect();
125
- }, timeout) as unknown as number;
126
- }
127
-
128
- public send<T>(data: T) {
129
- const send = () => {
130
- this.socket?.send(JSON.stringify(data));
131
- }
132
-
133
- if (this.socket?.readyState !== WebSocket.OPEN) {
134
- console.warn(`QuotesDataSocket: socket is not ready to send data`);
135
- this.socket.addEventListener("open", send, { once: true });
136
- return;
137
- }
138
-
139
- try {
140
- send();
141
- } catch (e) {
142
- console.error(`QuotesDataSocket: error, while sending data to WS server`);
143
- console.error(e);
144
- }
145
- }
146
- }
@@ -1,6 +0,0 @@
1
- export * from './safetyRun';
2
- export * from './useFullscreen';
3
- export * from './numberTools';
4
- export * from './useTabs';
5
- export * from './WebsocketClient';
6
- export * from './ApiError'
@@ -1,6 +0,0 @@
1
- export function dropTrailZeroes(str: string | undefined) {
2
- if (typeof str === 'string' && str.includes(".")) {
3
- return str.replace(/\.?0*$/, "");
4
- }
5
- return str;
6
- }
@@ -1,5 +0,0 @@
1
- export default function safetyRun<T>(promise?: Promise<T>): void {
2
- promise?.catch((e) => {
3
- console.error(e);
4
- })
5
- }
@@ -1,62 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons";
3
- import { Button } from "antd";
4
- import { createStyles } from "antd-style";
5
-
6
- function changeFullScreen(fullscreen: boolean) {
7
- if (fullscreen) {
8
- document.documentElement.requestFullscreen().catch((e) => {
9
- console.log(e);
10
- });
11
- } else if (document.fullscreenElement) {
12
- document.exitFullscreen().catch((e) => {
13
- console.log(e);
14
- });
15
- }
16
- }
17
-
18
- const useStyles = createStyles(({ token }) => {
19
- return {
20
- fullscreen: {
21
- backgroundColor: token.colorBgLayout,
22
- position: 'fixed',
23
- top: 0,
24
- left: 0,
25
- zIndex: 100,
26
- width: '100%',
27
- height: '100%',
28
- overflow: 'auto',
29
- },
30
- notFullscreen: {},
31
- };
32
- });
33
-
34
- export function useFullscreen() {
35
- const [isFullscreen, setIsFullscreen] = useState(false);
36
- const { styles } = useStyles();
37
-
38
- useEffect(() => {
39
- const listener = () => {
40
- setIsFullscreen(!!document.fullscreenElement);
41
- };
42
- document.addEventListener('fullscreenchange', listener);
43
- return () => {
44
- document.removeEventListener('fullscreenchange', listener);
45
- }
46
- }, []);
47
-
48
- const button = <Button
49
- key="fullscreen"
50
- type="text"
51
- onClick={() => changeFullScreen(!isFullscreen)}
52
- >
53
- {isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
54
- </Button>;
55
-
56
- return {
57
- isFullscreen,
58
- setIsFullscreen,
59
- fullscreenClassName: isFullscreen ? styles.fullscreen : styles.notFullscreen,
60
- fullscreenButton: button,
61
- };
62
- }
@@ -1,17 +0,0 @@
1
- import { useSearchParams } from "react-router-dom";
2
- import React, { useEffect, useState } from "react";
3
-
4
- export function useTabs<T extends string>(defaultTab: T): [T, React.Dispatch<React.SetStateAction<T>>] {
5
- const [searchParams, setSearchParams] = useSearchParams();
6
- const [tab, setTab] = useState<T>(() => {
7
- return searchParams.get('tab') as T ?? defaultTab;
8
- });
9
-
10
- useEffect(() => {
11
- const newSearchParams = new URLSearchParams(searchParams);
12
- newSearchParams.set('tab', tab);
13
- setSearchParams(newSearchParams, { replace: true });
14
- }, [tab]);
15
-
16
- return [tab, setTab];
17
- }