@gisce/react-ooui 2.0.0-rc.6 → 2.0.0-rc.8
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/dist/actionbar/TreeActionBar.d.ts.map +1 -1
- package/dist/hooks/useSearch.d.ts +1 -0
- package/dist/hooks/useSearch.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/locales/ca_ES.d.ts +4 -0
- package/dist/locales/ca_ES.d.ts.map +1 -1
- package/dist/locales/en_US.d.ts +4 -0
- package/dist/locales/en_US.d.ts.map +1 -1
- package/dist/locales/es_ES.d.ts +4 -0
- package/dist/locales/es_ES.d.ts.map +1 -1
- package/dist/react-ooui.es.js +4602 -4412
- package/dist/react-ooui.es.js.map +1 -1
- package/dist/react-ooui.umd.js +15 -14
- package/dist/react-ooui.umd.js.map +1 -1
- package/dist/widgets/WidgetFactory.d.ts.map +1 -1
- package/dist/widgets/custom/Avatar.d.ts.map +1 -1
- package/dist/widgets/custom/Comments.d.ts +35 -0
- package/dist/widgets/custom/Comments.d.ts.map +1 -0
- package/dist/widgets/views/Form.d.ts.map +1 -1
- package/dist/widgets/views/SearchTree.d.ts.map +1 -1
- package/dist/widgets/views/Tree.d.ts +1 -0
- package/dist/widgets/views/Tree.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/actionbar/TreeActionBar.tsx +75 -80
- package/src/helpers/iconMapper.ts +1 -2
- package/src/hooks/useSearch.ts +14 -3
- package/src/index.ts +2 -0
- package/src/locales/ca_ES.tsx +5 -0
- package/src/locales/en_US.tsx +5 -0
- package/src/locales/es_ES.tsx +5 -0
- package/src/stories/custom/Comments.stories.tsx +190 -0
- package/src/stories/dashboard/DashboardGrid.stories.tsx +13 -0
- package/src/widgets/WidgetFactory.tsx +3 -0
- package/src/widgets/custom/Avatar.tsx +0 -1
- package/src/widgets/custom/Comments.tsx +144 -0
- package/src/widgets/views/Form.tsx +18 -1
- package/src/widgets/views/Graph/GraphIndicator.tsx +3 -3
- package/src/widgets/views/SearchTree.tsx +9 -4
- package/src/widgets/views/Tree.tsx +9 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, {useContext} from "react";
|
|
2
|
+
import Field from "@/common/Field";
|
|
3
|
+
import { WidgetProps } from "@/types";
|
|
4
|
+
import {Card, Space, Typography, Avatar, Tag, Timeline, TimelineItemProps} from "antd"
|
|
5
|
+
import { LocaleContext, LocaleContextType } from "@/context/LocaleContext";
|
|
6
|
+
|
|
7
|
+
const { Meta } = Card;
|
|
8
|
+
const { Text} = Typography;
|
|
9
|
+
|
|
10
|
+
import dayjs from "dayjs";
|
|
11
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
12
|
+
import md5 from "md5";
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
dayjs.extend(relativeTime);
|
|
16
|
+
|
|
17
|
+
type GravatarProps = {
|
|
18
|
+
email: string,
|
|
19
|
+
size?: number,
|
|
20
|
+
theme: string,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type CommentDataType = {
|
|
24
|
+
id: number,
|
|
25
|
+
isSender: boolean,
|
|
26
|
+
isAuthor: boolean,
|
|
27
|
+
text: string,
|
|
28
|
+
email: string,
|
|
29
|
+
date: string,
|
|
30
|
+
author: string,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type CommentProps = {
|
|
34
|
+
data: CommentDataType,
|
|
35
|
+
style?: any,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type CommentsTypeProps = {
|
|
39
|
+
data: CommentDataType[],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type CommentsTimelineProps = {
|
|
43
|
+
value?: EventsType[],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
type EventActionType = {
|
|
48
|
+
date: string,
|
|
49
|
+
action: string,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type EventsType = {
|
|
53
|
+
type: "action" | "comment",
|
|
54
|
+
event: EventActionType | CommentDataType
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const Gravatar = ({ email, size = 40, theme = '' } : GravatarProps) => {
|
|
59
|
+
// Genera el enlace de Gravatar utilizando el email
|
|
60
|
+
const gravatarUrl = `https://www.gravatar.com/avatar/${md5(email)}?s=${size}&d=${theme}`;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Avatar src={gravatarUrl} size={size} />
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
function TextWithNewlines({ text }:{text: string}) {
|
|
69
|
+
const textParts = text.split('\n');
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
{textParts.map((part, index) => (
|
|
74
|
+
<React.Fragment key={index}>
|
|
75
|
+
{part}
|
|
76
|
+
{index !== textParts.length - 1 && <br />}
|
|
77
|
+
</React.Fragment>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const Comment = (props: CommentProps) => {
|
|
84
|
+
const {data, style} = props;
|
|
85
|
+
const { t } = useContext(LocaleContext) as LocaleContextType;
|
|
86
|
+
return (
|
|
87
|
+
<Card key={data.id} style={{...style, ...{textAlign: 'start'}}}>
|
|
88
|
+
<Meta avatar={<Gravatar email={data.email} theme='mp'/>}
|
|
89
|
+
title={data.author}
|
|
90
|
+
description={(
|
|
91
|
+
<Space direction="horizontal">
|
|
92
|
+
<span title={data.date}>{dayjs(data.date).fromNow()}</span>
|
|
93
|
+
{data.isAuthor && <Tag color="blue">{t('author')}</Tag>}
|
|
94
|
+
</Space>
|
|
95
|
+
)} style={{marginBottom: '5px'}}/>
|
|
96
|
+
<Text>
|
|
97
|
+
<TextWithNewlines text={data.text} />
|
|
98
|
+
</Text>
|
|
99
|
+
</Card>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const Comments = (props: CommentsTypeProps) => {
|
|
104
|
+
const { data = [] } = props;
|
|
105
|
+
const comments = data.map((c) => {
|
|
106
|
+
const style = {float: c.isSender ? 'right' : 'left', width: '60%'};
|
|
107
|
+
return <Comment data={c} style={style}/>
|
|
108
|
+
});
|
|
109
|
+
return (
|
|
110
|
+
<div>
|
|
111
|
+
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
|
|
112
|
+
{comments}
|
|
113
|
+
</Space>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
export const CommentsTimeline = (props: CommentsTimelineProps) => {
|
|
120
|
+
const { value } = props;
|
|
121
|
+
const items = (value || []).map(i => {
|
|
122
|
+
if (i.type === 'action') {
|
|
123
|
+
return {
|
|
124
|
+
children: `${i.event.date} - ${(i.event as EventActionType).action}`
|
|
125
|
+
}
|
|
126
|
+
} else if (i.type === "comment") {
|
|
127
|
+
return {
|
|
128
|
+
color: 'gray',
|
|
129
|
+
position: (i.event as CommentDataType).isSender ? 'left' : 'right',
|
|
130
|
+
label: i.event.date,
|
|
131
|
+
children: <Comment data={i.event as CommentDataType} />,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
return <Timeline mode="alternate" items={items as TimelineItemProps[]} />
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const CommentsTimelineField = (props: WidgetProps) => {
|
|
139
|
+
return (
|
|
140
|
+
<Field {...props}>
|
|
141
|
+
<CommentsTimeline />
|
|
142
|
+
</Field>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -705,15 +705,17 @@ function Form(props: FormProps, ref: any) {
|
|
|
705
705
|
fields,
|
|
706
706
|
arch,
|
|
707
707
|
values,
|
|
708
|
+
operationInProgress = false,
|
|
708
709
|
}: {
|
|
709
710
|
arch: string;
|
|
710
711
|
fields: any;
|
|
711
712
|
values: any;
|
|
713
|
+
operationInProgress?: boolean;
|
|
712
714
|
}) => {
|
|
713
715
|
const ooui = new FormOoui(fields);
|
|
714
716
|
// TODO: Here we must inject `values` to the ooui parser in order to evaluate arch+values and get the new form container
|
|
715
717
|
ooui.parse(arch, {
|
|
716
|
-
readOnly,
|
|
718
|
+
readOnly: readOnly || operationInProgress,
|
|
717
719
|
values: convertToPlain2ManyValues(
|
|
718
720
|
{
|
|
719
721
|
...values,
|
|
@@ -980,6 +982,15 @@ function Form(props: FormProps, ref: any) {
|
|
|
980
982
|
checkFieldsChanges({ elementHasLostFocus: true });
|
|
981
983
|
}
|
|
982
984
|
|
|
985
|
+
function updateOperationInProgress(value: boolean) {
|
|
986
|
+
parseForm({
|
|
987
|
+
fields,
|
|
988
|
+
arch: arch!,
|
|
989
|
+
values: getCurrentValues(fields),
|
|
990
|
+
operationInProgress: value,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
|
|
983
994
|
async function executeButtonAction({
|
|
984
995
|
type,
|
|
985
996
|
action,
|
|
@@ -1001,8 +1012,12 @@ function Form(props: FormProps, ref: any) {
|
|
|
1001
1012
|
return;
|
|
1002
1013
|
}
|
|
1003
1014
|
|
|
1015
|
+
let mustBlockButtons = false;
|
|
1016
|
+
|
|
1004
1017
|
try {
|
|
1005
1018
|
if (formHasChanges() || getCurrentId() === undefined) {
|
|
1019
|
+
mustBlockButtons = true;
|
|
1020
|
+
updateOperationInProgress(true);
|
|
1006
1021
|
if (submitMode === "2many") {
|
|
1007
1022
|
await submitApi({ callOnSubmitSucceed: false });
|
|
1008
1023
|
x2manyPendingLink.current = true;
|
|
@@ -1024,7 +1039,9 @@ function Form(props: FormProps, ref: any) {
|
|
|
1024
1039
|
} else if (type === "action") {
|
|
1025
1040
|
await runActionButton({ action, context: updatedContext });
|
|
1026
1041
|
}
|
|
1042
|
+
mustBlockButtons && updateOperationInProgress(false);
|
|
1027
1043
|
} catch (err) {
|
|
1044
|
+
mustBlockButtons && updateOperationInProgress(false);
|
|
1028
1045
|
showErrorDialog(err);
|
|
1029
1046
|
}
|
|
1030
1047
|
}
|
|
@@ -297,7 +297,7 @@ function CommonIndicator({
|
|
|
297
297
|
fontSize = maxFontSize;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
const IconElement: any = icon && iconMapper(icon);
|
|
300
|
+
const IconElement: any = icon && iconMapper(icon, {style: {fontSize: fontSize * 0.7}});
|
|
301
301
|
|
|
302
302
|
return (
|
|
303
303
|
<div
|
|
@@ -373,13 +373,13 @@ function PercentageIndicator({
|
|
|
373
373
|
tw = tw * 1.5;
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
const IconElement: any = icon && iconMapper(icon);
|
|
377
|
-
|
|
378
376
|
if (tw >= width * 0.85 || fontSize * 2 < twoLinesHeight) {
|
|
379
377
|
const maxFontSize = ((width * 0.85) / tw) * fontSize;
|
|
380
378
|
fontSize = maxFontSize;
|
|
381
379
|
}
|
|
382
380
|
|
|
381
|
+
const IconElement: any = icon && iconMapper(icon, {style: {fontSize}});
|
|
382
|
+
|
|
383
383
|
return (
|
|
384
384
|
<div
|
|
385
385
|
ref={measureRef}
|
|
@@ -125,6 +125,7 @@ function SearchTree(props: Props, ref: any) {
|
|
|
125
125
|
colorsForResults,
|
|
126
126
|
statusForResults,
|
|
127
127
|
totalItems,
|
|
128
|
+
getAllIds,
|
|
128
129
|
} = useSearch({
|
|
129
130
|
model: currentModel!,
|
|
130
131
|
setSearchTreeNameSearch,
|
|
@@ -269,13 +270,16 @@ function SearchTree(props: Props, ref: any) {
|
|
|
269
270
|
};
|
|
270
271
|
|
|
271
272
|
function changeSelectedRowKeys(selectedRowKeys: any[]) {
|
|
272
|
-
|
|
273
|
-
selectedRowKeys.includes(entry.id)
|
|
274
|
-
);
|
|
275
|
-
setSelectedRowItems?.(selectedRowItems);
|
|
273
|
+
setSelectedRowItems?.(selectedRowKeys.map((id: number) => ({ id })));
|
|
276
274
|
onChangeSelectedRowKeys?.(selectedRowKeys);
|
|
277
275
|
}
|
|
278
276
|
|
|
277
|
+
async function selectAllRecords() {
|
|
278
|
+
const allIds = await getAllIds();
|
|
279
|
+
setSelectedRowItems?.(allIds.map((id: number) => ({ id })));
|
|
280
|
+
onChangeSelectedRowKeys?.(allIds);
|
|
281
|
+
}
|
|
282
|
+
|
|
279
283
|
function calculateTableHeight() {
|
|
280
284
|
if (treeView?.isExpandable) {
|
|
281
285
|
return height - 160;
|
|
@@ -331,6 +335,7 @@ function SearchTree(props: Props, ref: any) {
|
|
|
331
335
|
}
|
|
332
336
|
childField={treeView.field_parent}
|
|
333
337
|
rootTree={rootTree}
|
|
338
|
+
onSelectAllRecords={selectAllRecords}
|
|
334
339
|
/>
|
|
335
340
|
</>
|
|
336
341
|
);
|
|
@@ -53,6 +53,7 @@ type Props = {
|
|
|
53
53
|
rootTree?: boolean;
|
|
54
54
|
context?: any;
|
|
55
55
|
readonly?: boolean;
|
|
56
|
+
onSelectAllRecords?: () => Promise<void>;
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
const booleanComponentFn = (value: boolean): React.ReactElement => {
|
|
@@ -190,6 +191,7 @@ function Tree(props: Props): React.ReactElement {
|
|
|
190
191
|
rootTree = false,
|
|
191
192
|
context,
|
|
192
193
|
readonly,
|
|
194
|
+
onSelectAllRecords,
|
|
193
195
|
} = props;
|
|
194
196
|
|
|
195
197
|
const [items, setItems] = useState<Array<any>>([]);
|
|
@@ -382,6 +384,13 @@ function Tree(props: Props): React.ReactElement {
|
|
|
382
384
|
}
|
|
383
385
|
: undefined
|
|
384
386
|
}
|
|
387
|
+
onSelectAllRecords={onSelectAllRecords}
|
|
388
|
+
totalItems={total || 0}
|
|
389
|
+
translations={{
|
|
390
|
+
recordsSelected: t("recordsSelected"),
|
|
391
|
+
selectAllRecords: t("selectAllRecords"),
|
|
392
|
+
allRecordsSelected: t("allRecordsSelected"),
|
|
393
|
+
}}
|
|
385
394
|
/>
|
|
386
395
|
{getSums()}
|
|
387
396
|
</div>
|