@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.
Files changed (40) hide show
  1. package/dist/actionbar/TreeActionBar.d.ts.map +1 -1
  2. package/dist/hooks/useSearch.d.ts +1 -0
  3. package/dist/hooks/useSearch.d.ts.map +1 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/locales/ca_ES.d.ts +4 -0
  7. package/dist/locales/ca_ES.d.ts.map +1 -1
  8. package/dist/locales/en_US.d.ts +4 -0
  9. package/dist/locales/en_US.d.ts.map +1 -1
  10. package/dist/locales/es_ES.d.ts +4 -0
  11. package/dist/locales/es_ES.d.ts.map +1 -1
  12. package/dist/react-ooui.es.js +4602 -4412
  13. package/dist/react-ooui.es.js.map +1 -1
  14. package/dist/react-ooui.umd.js +15 -14
  15. package/dist/react-ooui.umd.js.map +1 -1
  16. package/dist/widgets/WidgetFactory.d.ts.map +1 -1
  17. package/dist/widgets/custom/Avatar.d.ts.map +1 -1
  18. package/dist/widgets/custom/Comments.d.ts +35 -0
  19. package/dist/widgets/custom/Comments.d.ts.map +1 -0
  20. package/dist/widgets/views/Form.d.ts.map +1 -1
  21. package/dist/widgets/views/SearchTree.d.ts.map +1 -1
  22. package/dist/widgets/views/Tree.d.ts +1 -0
  23. package/dist/widgets/views/Tree.d.ts.map +1 -1
  24. package/package.json +4 -2
  25. package/src/actionbar/TreeActionBar.tsx +75 -80
  26. package/src/helpers/iconMapper.ts +1 -2
  27. package/src/hooks/useSearch.ts +14 -3
  28. package/src/index.ts +2 -0
  29. package/src/locales/ca_ES.tsx +5 -0
  30. package/src/locales/en_US.tsx +5 -0
  31. package/src/locales/es_ES.tsx +5 -0
  32. package/src/stories/custom/Comments.stories.tsx +190 -0
  33. package/src/stories/dashboard/DashboardGrid.stories.tsx +13 -0
  34. package/src/widgets/WidgetFactory.tsx +3 -0
  35. package/src/widgets/custom/Avatar.tsx +0 -1
  36. package/src/widgets/custom/Comments.tsx +144 -0
  37. package/src/widgets/views/Form.tsx +18 -1
  38. package/src/widgets/views/Graph/GraphIndicator.tsx +3 -3
  39. package/src/widgets/views/SearchTree.tsx +9 -4
  40. 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
- const selectedRowItems = getResults().filter((entry: any) =>
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>