@genesislcap/blank-app-seed 5.2.0 → 5.3.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/blank-app-seed-config",
3
3
  "description": "Genesis Blank App Seed Configuration",
4
- "version": "5.2.0",
4
+ "version": "5.3.1",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "lint": "eslint .",
@@ -0,0 +1,32 @@
1
+ import { CustomEventHandler } from "../../../utils/customEvents";
2
+
3
+ {{#each tile.config.customEvents}}
4
+ {{#if this.hasForm}}
5
+ export const {{camelCase this.name}}FormSchema = {{{this.uischema}}};
6
+ {{/if}}
7
+ {{/each}}
8
+
9
+ export const customEventFormSchemas = {
10
+ {{#each tile.config.customEvents}}
11
+ {{#if this.hasForm}}
12
+ '{{this.name}}': {{camelCase this.name}}FormSchema,
13
+ {{/if}}
14
+ {{/each}}
15
+ };
16
+
17
+ export const customEvents: CustomEventHandler[] = [
18
+ {{#each tile.config.customEvents}}
19
+ {
20
+ baseEvent: '{{this.baseEvent}}',
21
+ name: '{{this.name}}',
22
+ hasForm: {{this.hasForm}},
23
+ {{#if this.confirmSubmit}}
24
+ confirmSubmit: {
25
+ state: '{{this.confirmSubmit.state}}',
26
+ message: '{{this.confirmSubmit.message}}'
27
+ },
28
+ {{/if}}
29
+ defaultValues: {{{this.defaultValues}}},
30
+ },
31
+ {{/each}}
32
+ ];
@@ -1,16 +1,27 @@
1
1
  {{#if tile.config.permissions.viewRight~}}
2
2
  import { getUser } from '@genesislcap/foundation-user';
3
- import { getViewUpdateRightComponent } from '../../../utils';
3
+ {{#if tile.config.customEvents}}
4
+ import { customEvent } from '@genesislcap/foundation-events';
5
+ {{/if}}
6
+ import { getViewUpdateRightComponent{{#if tile.config.customEvents}}, submitFailureNotification{{/if}} } from '../../../utils';
4
7
  import ErrorMessage from '../../../components/ErrorMessage/ErrorMessage';
5
8
  {{else if tile.config.permissions.updateRight~}}
6
9
  import { getUser } from '@genesislcap/foundation-user';
7
- import { getViewUpdateRightComponent } from '../../../utils';
10
+ {{#if tile.config.customEvents}}
11
+ import { customEvent } from '@genesislcap/foundation-events';
12
+ {{/if}}
13
+ import { getViewUpdateRightComponent{{#if tile.config.customEvents}}, submitFailureNotification{{/if}} } from '../../../utils';
8
14
  import ErrorMessage from '../../../components/ErrorMessage/ErrorMessage';
9
15
  {{/if}}
10
16
  {{#if tile.config.customEvents}}
17
+ import { useState, useRef, useEffect } from 'react';
18
+ import { createPortal } from 'react-dom';
11
19
  import { getConnect } from '@genesislcap/foundation-comms';
12
20
  import type { ActionRendererParams } from '@genesislcap/rapid-grid-pro';
13
21
  import { RapidAgActionRenderer } from '@genesislcap/rapid-grid-pro';
22
+ import { Modal } from '@genesislcap/rapid-design-system';
23
+ import { customEvents, customEventFormSchemas } from './{{pascalCase tile.title}}EventsConfig';
24
+ import { useCustomEvent, type CustomEventHandler, type CustomEventState } from '../../../utils/customEvents';
14
25
  {{/if}}
15
26
  {{#if tile.config.createFormUiSchema~}}
16
27
  import { createFormSchema as createFormSchemaTile } from './{{pascalCase tile.title}}CreateFormSchema';
@@ -33,7 +44,7 @@ import './{{pascalCase tile.title}}Component.css';
33
44
  {{#ifAny tile.metadata.comment tile.metadata.todo}}
34
45
  /**
35
46
  {{~#if tile.metadata.comment}}{{{ tile.metadata.comment }}}{{/if}}
36
- {{~#if tile.metadata.todo}}{{#if tile.metadata.comment}}
47
+ {{~#if tile.metadata.todo}}{{#if tile.metadata.comment}}
37
48
  *{{/if}}
38
49
  * TODO: {{{ tile.metadata.todo }}}{{/if}}
39
50
  */
@@ -68,17 +79,24 @@ export const {{pascalCase tile.componentName}}: React.FC = () => {
68
79
  headerName: '',
69
80
  minWidth: 50,
70
81
  maxWidth: 50,
82
+ headerTooltip: '{{this.tooltip}}',
71
83
  pinned: 'right',
72
84
  cellRenderer: RapidAgActionRenderer,
73
85
  cellRendererParams: {
74
86
  actionClick: async (rowData) => {
75
- const { ROW_REF, ...DETAILS } = rowData;
76
- const response = await getConnect().commitEvent("EVENT_{{this}}", {
77
- DETAILS,
78
- });
87
+ const customEvent = customEvents.find(e => e.name === '{{this.name}}');
88
+ if (customEvent) {
89
+ const handleCustomEvent = useCustomEvent(
90
+ customEvent,
91
+ rowData,
92
+ setCustomEventFormData,
93
+ setActiveCustomEvent
94
+ );
95
+ await handleCustomEvent();
96
+ }
79
97
  },
80
98
  contentTemplate: `
81
- <rapid-icon name="cog"></rapid-icon>
99
+ <rapid-icon name="{{this.icon}}"></rapid-icon>
82
100
  `,
83
101
  } as ActionRendererParams,
84
102
  },
@@ -114,7 +132,70 @@ export const {{pascalCase tile.componentName}}: React.FC = () => {
114
132
  };
115
133
  {{/if}}
116
134
 
135
+ {{#if tile.config.customEvents}}
136
+ const customEventModalRef = useRef<Modal>(null);
137
+ const [activeCustomEvent, setActiveCustomEvent] = useState<CustomEventState | null>(null);
138
+ const [customEventFormData, setCustomEventFormData] = useState<Record<string, any>>({});
139
+
140
+ useEffect(() => {
141
+ if (activeCustomEvent) {
142
+ customEventModalRef.current?.show();
143
+ } else {
144
+ customEventModalRef.current?.close();
145
+ }
146
+ }, [activeCustomEvent]);
147
+
148
+ const handleCustomEventSubmit = () => {
149
+ setActiveCustomEvent(null);
150
+ setCustomEventFormData({});
151
+ };
152
+
153
+ const getActiveCustomEvent = () => {
154
+ if (!activeCustomEvent) return null;
155
+ return customEvents.find(e => e.name === activeCustomEvent.name);
156
+ };
157
+
158
+ const getCustomEventModalTitle = () => {
159
+ const customEvent = getActiveCustomEvent();
160
+ return customEvent?.name || 'Custom Event';
161
+ };
162
+
163
+ const getCustomEventResourceName = () => {
164
+ if (!activeCustomEvent) return '';
165
+ return `EVENT_${activeCustomEvent.event}`;
166
+ };
167
+
168
+ const getCustomEventUiSchema = () => {
169
+ const customEvent = getActiveCustomEvent();
170
+ return customEvent && customEvent.name in customEventFormSchemas
171
+ ? customEventFormSchemas[customEvent.name as keyof typeof customEventFormSchemas]
172
+ : null;
173
+ };
174
+
175
+ const renderFormModal = () => {
176
+ if (!activeCustomEvent) return null;
177
+
178
+ return createPortal(
179
+ <rapid-modal
180
+ ref={customEventModalRef}
181
+ onCloseCallback={() => setActiveCustomEvent(null)}
182
+ >
183
+ <h4 slot="top">{getCustomEventModalTitle()}</h4>
184
+ <foundation-form
185
+ resourceName={getCustomEventResourceName()}
186
+ uischema={getCustomEventUiSchema()}
187
+ data={customEventFormData}
188
+ onsubmit-success={handleCustomEventSubmit}
189
+ onsubmit={submitFailureNotification}
190
+ onsubmit-failure={submitFailureNotification}
191
+ ></foundation-form>
192
+ </rapid-modal>,
193
+ document.querySelector('rapid-design-system-provider')!
194
+ );
195
+ };
196
+ {{/if}}
197
+
117
198
  return (
118
199
  {{> (lookup tile 'type') tile}}
119
200
  );
120
- };
201
+ };
@@ -1,63 +1,68 @@
1
1
  {{#if config.permissions.viewRight~}}
2
2
  hasUserPermission('{{config.permissions.viewRight}}') ? (
3
3
  {{/if}}
4
- <entity-management
5
- design-system-prefix="rapid"
6
- enable-row-flashing
7
- enable-cell-flashing
8
- {{#if config.title~}}
9
- title="{{ config.title }}"
10
- {{/if}}
11
- resourceName="{{ config.resourceName }}"
12
- {{#if config.createEvent~}}
13
- {{#if config.permissions.updateRight~}}
14
- createEvent={hasUserPermission('{{config.permissions.updateRight}}') ? '{{ config.createEvent }}' : undefined}
15
- {{else~}}
16
- createEvent="{{ config.createEvent }}"
17
- {{/if}}
18
- {{#if config.createFormUiSchema~}}
19
- createFormUiSchema={createFormSchema}
20
- {{/if}}
21
- {{/if}}
22
- {{#if config.updateEvent~}}
23
- {{#if config.permissions.updateRight~}}
24
- updateEvent={hasUserPermission('{{config.permissions.updateRight}}') ? '{{ config.updateEvent }}' : undefined}
25
- {{else~}}
26
- updateEvent="{{ config.updateEvent }}"
27
- {{/if}}
28
- {{#if config.updateFormUiSchema~}}
29
- updateFormUiSchema={updateFormSchema}
30
- {{/if}}
31
- {{/if}}
32
- {{#if config.deleteEvent~}}
33
- {{#if config.permissions.updateRight~}}
34
- deleteEvent={hasUserPermission('{{config.permissions.updateRight}}') ? '{{ config.deleteEvent }}' : undefined}
35
- {{else~}}
36
- deleteEvent="{{ config.deleteEvent }}"
37
- {{/if}}
38
- {{/if}}
39
- {{#if config.gridOptions~}}
40
- gridOptions={gridOptions}
41
- {{/if}}
42
- {{#if config.snapshot~}}
43
- datasourceConfig={ isSnapshot: {{ config.snapshot }} }
44
- {{/if}}
45
- {{#if config.reqrep~}}
46
- datasourceConfig={reqrep}
47
- {{/if}}
48
- {{#if config.columns~}}
49
- columns={columnDefs}
50
- {{/if}}
51
- {{#if config.modalPosition~}}
52
- modal-position="{{ config.modalPosition }}"
53
- {{/if}}
54
- {{#if config.sizeColumnsToFit~}}
55
- size-columns-to-fit
56
- {{/if}}
57
- {{#if config.enableSearchBar~}}
58
- enable-search-bar
59
- {{/if}}
60
- ></entity-management>
4
+ <>
5
+ <entity-management
6
+ design-system-prefix="rapid"
7
+ enable-row-flashing
8
+ enable-cell-flashing
9
+ {{#if config.title~}}
10
+ title="{{ config.title }}"
11
+ {{/if}}
12
+ resourceName="{{ config.resourceName }}"
13
+ {{#if config.createEvent~}}
14
+ {{#if config.permissions.updateRight~}}
15
+ createEvent={hasUserPermission('{{config.permissions.updateRight}}') ? '{{ config.createEvent }}' : undefined}
16
+ {{else~}}
17
+ createEvent="{{ config.createEvent }}"
18
+ {{/if}}
19
+ {{#if config.createFormUiSchema~}}
20
+ createFormUiSchema={createFormSchema}
21
+ {{/if}}
22
+ {{/if}}
23
+ {{#if config.updateEvent~}}
24
+ {{#if config.permissions.updateRight~}}
25
+ updateEvent={hasUserPermission('{{config.permissions.updateRight}}') ? '{{ config.updateEvent }}' : undefined}
26
+ {{else~}}
27
+ updateEvent="{{ config.updateEvent }}"
28
+ {{/if}}
29
+ {{#if config.updateFormUiSchema~}}
30
+ updateFormUiSchema={updateFormSchema}
31
+ {{/if}}
32
+ {{/if}}
33
+ {{#if config.deleteEvent~}}
34
+ {{#if config.permissions.updateRight~}}
35
+ deleteEvent={hasUserPermission('{{config.permissions.updateRight}}') ? '{{ config.deleteEvent }}' : undefined}
36
+ {{else~}}
37
+ deleteEvent="{{ config.deleteEvent }}"
38
+ {{/if}}
39
+ {{/if}}
40
+ {{#if config.gridOptions~}}
41
+ gridOptions={gridOptions}
42
+ {{/if}}
43
+ {{#if config.snapshot~}}
44
+ datasourceConfig={ isSnapshot: {{ config.snapshot }} }
45
+ {{/if}}
46
+ {{#if config.reqrep~}}
47
+ datasourceConfig={reqrep}
48
+ {{/if}}
49
+ {{#if config.columns~}}
50
+ columns={columnDefs}
51
+ {{/if}}
52
+ {{#if config.modalPosition~}}
53
+ modal-position="{{ config.modalPosition }}"
54
+ {{/if}}
55
+ {{#if config.sizeColumnsToFit~}}
56
+ size-columns-to-fit
57
+ {{/if}}
58
+ {{#if config.enableSearchBar~}}
59
+ enable-search-bar
60
+ {{/if}}
61
+ ></entity-management>
62
+ {{#if config.customEvents}}
63
+ {renderFormModal()}
64
+ {{/if}}
65
+ </>
61
66
  {{#if config.permissions.viewRight~}}
62
67
  ) : (
63
68
  <ErrorMessage elementType="h3" message="You do not have access to view this component." />
@@ -0,0 +1,32 @@
1
+ import { CustomEventHandler } from "../../../utils/customEvents";
2
+
3
+ {{#each tile.config.customEvents}}
4
+ {{#if this.hasForm}}
5
+ export const {{camelCase this.name}}FormSchema = {{{this.uischema}}};
6
+ {{/if}}
7
+ {{/each}}
8
+
9
+ export const customEventFormSchemas = {
10
+ {{#each tile.config.customEvents}}
11
+ {{#if this.hasForm}}
12
+ '{{this.name}}': {{camelCase this.name}}FormSchema,
13
+ {{/if}}
14
+ {{/each}}
15
+ };
16
+
17
+ export const customEvents: CustomEventHandler[] = [
18
+ {{#each tile.config.customEvents}}
19
+ {
20
+ baseEvent: '{{this.baseEvent}}',
21
+ name: '{{this.name}}',
22
+ hasForm: {{this.hasForm}},
23
+ {{#if this.confirmSubmit}}
24
+ confirmSubmit: {
25
+ state: '{{this.confirmSubmit.state}}',
26
+ message: '{{this.confirmSubmit.message}}'
27
+ },
28
+ {{/if}}
29
+ defaultValues: {{{this.defaultValues}}},
30
+ },
31
+ {{/each}}
32
+ ];
@@ -10,6 +10,9 @@ import { ColDef } from '@ag-grid-community/core';
10
10
  {{#if tile.config.customEvents}}
11
11
  import type { ActionRendererParams } from '@genesislcap/rapid-grid-pro';
12
12
  import { RapidAgActionRenderer } from '@genesislcap/rapid-grid-pro';
13
+ import { Modal } from '@genesislcap/rapid-design-system';
14
+ import { customEvents, customEventFormSchemas } from './{{kebabCase tile.title}}.events.config';
15
+ import { handleFormCustomEvent, handleNonFormCustomEvent, showCustomEventConfirmation, type CustomEventHandler, type CustomEventState } from '../../../utils/customEvents';
13
16
  {{/if}}
14
17
 
15
18
  {{#ifAny tile.metadata.comment tile.metadata.todo}}
@@ -30,32 +33,81 @@ export class {{pascalCase tile.componentName}} extends GenesisElement {
30
33
  @User user: User;
31
34
  @Connect connect!: Connect;
32
35
 
36
+ {{#if tile.config.customEvents}}
37
+ customEventModal: Modal;
38
+ @observable customEvents: CustomEventHandler[] = customEvents;
39
+ @observable activeCustomEvent: CustomEventState | null = null;
40
+ @observable customEventFormData: Record<string, any> = {};
41
+
42
+ async handleCustomEventSubmit() {
43
+ this.activeCustomEvent = null;
44
+ this.customEventFormData = {};
45
+ this.customEventModal.close();
46
+ }
47
+
48
+ async handleCustomEventClick(eventName: string, rowData: any) {
49
+ const customEvent = this.customEvents.find(e => e.name === eventName);
50
+ if (!customEvent) return;
51
+
52
+ if (customEvent.hasForm) {
53
+ handleFormCustomEvent(
54
+ customEvent,
55
+ rowData,
56
+ (data) => this.customEventFormData = data,
57
+ (event) => this.activeCustomEvent = event,
58
+ () => this.customEventModal.show()
59
+ );
60
+ } else {
61
+ await handleNonFormCustomEvent(
62
+ this.connect,
63
+ customEvent,
64
+ rowData,
65
+ (onConfirm) => showCustomEventConfirmation(customEvent, onConfirm)
66
+ );
67
+ }
68
+ }
69
+
70
+ getActiveCustomEvent() {
71
+ if (!this.activeCustomEvent) return null;
72
+ return this.customEvents.find(e => e.name === this.activeCustomEvent.name);
73
+ }
74
+
75
+ getCustomEventModalTitle() {
76
+ const customEvent = this.getActiveCustomEvent();
77
+ return customEvent?.name || 'Custom Event';
78
+ }
79
+
80
+ getCustomEventResourceName() {
81
+ if (!this.activeCustomEvent) return '';
82
+ return `EVENT_${this.activeCustomEvent.event}`;
83
+ }
84
+
85
+ getCustomEventUiSchema() {
86
+ const customEvent = this.getActiveCustomEvent();
87
+ if (!customEvent) return null;
88
+ return customEventFormSchemas[customEvent.name];
89
+ }
90
+ {{/if}}
91
+
33
92
  {{#if tile.config.columns}}
34
93
  @observable columns: ColDef[] = [
35
94
  ...columnDefs,
36
95
  {{#if tile.config.customEvents}}
37
- /**
38
- * TODO: Update the action button's icon under `contentTemplate` below.
39
- * More information about rapid-icon can be found at: https://docs.genesis.global/docs/develop/client-capabilities/presentation/client-presentation-icon/
40
- **/
41
96
  {{#each tile.config.customEvents}}
42
97
  {
43
98
  field: '',
44
99
  headerName: '',
45
100
  minWidth: 50,
46
101
  maxWidth: 50,
47
- headerTooltip: '{{capitalCase this}}',
102
+ headerTooltip: '{{this.tooltip}}',
48
103
  pinned: 'right',
49
104
  cellRenderer: RapidAgActionRenderer,
50
105
  cellRendererParams: <ActionRendererParams>{
51
106
  actionClick: async (rowData) => {
52
- const { ROW_REF, ...DETAILS } = rowData;
53
- const response = await this.connect.commitEvent("EVENT_{{this}}", {
54
- DETAILS,
55
- });
107
+ await this.handleCustomEventClick('{{this.name}}', rowData);
56
108
  },
57
109
  contentTemplate: `
58
- <rapid-icon name="cog"></rapid-icon>
110
+ <rapid-icon name="{{this.icon}}"></rapid-icon>
59
111
  `,
60
112
  },
61
113
  },
@@ -1,5 +1,8 @@
1
- import { html, whenElse, repeat } from '@genesislcap/web-core';
2
- import { getViewUpdateRightComponent } from '../../../utils';
1
+ import { html, when, whenElse, ref, repeat } from '@genesislcap/web-core';
2
+ {{#if tile.config.customEvents}}
3
+ import { customEvent } from '@genesislcap/foundation-events';
4
+ {{/if}}
5
+ import { getViewUpdateRightComponent{{#if tile.config.customEvents}}, submitFailureNotification{{/if}} } from '../../../utils';
3
6
  import type { {{pascalCase tile.componentName}} } from './{{kebabCase tile.title}}';
4
7
  {{#if tile.config.createFormUiSchema}}
5
8
  import { createFormSchema } from './{{kebabCase tile.title}}.create.form.schema';
@@ -1,6 +1,6 @@
1
1
  ${whenElse(
2
2
  (x) => getViewUpdateRightComponent(x.user, '{{config.permissions.viewRight}}'),
3
- html`
3
+ html<{{pascalCase componentName}}>`
4
4
  <entity-management
5
5
  design-system-prefix="rapid"
6
6
  header-case-type="capitalCase"
@@ -50,8 +50,29 @@ ${whenElse(
50
50
  enable-search-bar
51
51
  {{/if}}
52
52
  ></entity-management>
53
+ {{#if config.customEvents}}
54
+ <rapid-modal
55
+ ${ref('customEventModal')}
56
+ :onCloseCallback=${(x) => (x.activeCustomEvent = null)}
57
+ >
58
+ <h4 slot="top">${(x) => x.getCustomEventModalTitle()}</h4>
59
+ ${when(
60
+ (x) => x.activeCustomEvent,
61
+ html`
62
+ <foundation-form
63
+ resourceName=${(x) => x.getCustomEventResourceName()}
64
+ :uischema=${(x) => x.getCustomEventUiSchema()}
65
+ :data=${(x) => x.customEventFormData}
66
+ @submit-success=${(x) => x.handleCustomEventSubmit()}
67
+ @submit=${(_, c) => submitFailureNotification(customEvent(c))}
68
+ @submit-failure=${(_, c) => submitFailureNotification(customEvent(c))}
69
+ ></foundation-form>
70
+ `,
71
+ )}
72
+ </rapid-modal>
73
+ {{/if}}
53
74
  `,
54
75
  html`
55
76
  <not-permitted-component></not-permitted-component>
56
77
  `,
57
- )}
78
+ )}
@@ -7,6 +7,16 @@ const { getFormattedComment, getFormattedTodo } = require('./getTodosAndComments
7
7
  const getLayoutType = require('./getLayoutType');
8
8
  const { COMPONENT_TYPE, FRAMEWORK_ANGULAR_ALIAS } = require('../static');
9
9
 
10
+ const formatCustomEvents = (customEvents) => {
11
+ if (!customEvents) return;
12
+
13
+ return customEvents.map(event => ({
14
+ ...event,
15
+ uischema: event.hasForm ? formatJSONValue(event.uischema) : undefined,
16
+ defaultValues: event.defaultValues ? formatJSONValue(event.defaultValues) : undefined,
17
+ }));
18
+ };
19
+
10
20
  const formatRouteData = (framework, route) => {
11
21
  const layoutKey = route?.layoutKey || `${route.name}_${Date.now()}`;
12
22
  const layoutType =
@@ -32,6 +42,7 @@ const formatRouteData = (framework, route) => {
32
42
  updateFormUiSchema,
33
43
  uischema,
34
44
  columns,
45
+ customEvents,
35
46
  } = config;
36
47
 
37
48
  return {
@@ -47,6 +58,7 @@ const formatRouteData = (framework, route) => {
47
58
  updateFormUiSchema: formatJSONValue(updateFormUiSchema),
48
59
  uischema: formatJSONValue(uischema),
49
60
  columns: gridColumnsSerializer(columns),
61
+ customEvents: formatCustomEvents(customEvents),
50
62
  },
51
63
  metadata: {
52
64
  ...metadata,
@@ -17,6 +17,8 @@ const defaultPathGetters = {
17
17
  `${componentPath}/${tile.name}.column.defs.ts`,
18
18
  gridOptions: (componentPath, tile) =>
19
19
  `${componentPath}/${tile.name}.gridOptions.ts`,
20
+ customEventForm: (componentPath, tile) =>
21
+ `${componentPath}/${tile.name}.events.config.ts`,
20
22
  };
21
23
 
22
24
  const getPathByFramework = {
@@ -60,6 +62,8 @@ const getPathByFramework = {
60
62
  `${componentPath}/${changeCase.pascalCase(tile.name)}ColumnDefs.ts`,
61
63
  gridOptions: (componentPath, tile, changeCase) =>
62
64
  `${componentPath}/${changeCase.pascalCase(tile.name)}GridOptions.ts`,
65
+ customEventForm: (componentPath, tile, changeCase) =>
66
+ `${componentPath}/${changeCase.pascalCase(tile.name)}EventsConfig.ts`,
63
67
  },
64
68
  };
65
69
 
@@ -81,6 +85,7 @@ const getFilesToWrite = (
81
85
  updateForm: getUpdateFormTarget,
82
86
  columnDefs: getColumnDefsTarget,
83
87
  gridOptions: getGridOptionsTarget,
88
+ customEventForm: getCustomEventFormTarget,
84
89
  } = path;
85
90
 
86
91
  const routeDir = getRouteDir(clientSrcPath, tileData, routeName, changeCase);
@@ -120,6 +125,11 @@ const getFilesToWrite = (
120
125
  target: getGridOptionsTarget(routeDir, tileData, changeCase),
121
126
  };
122
127
 
128
+ const componentCustomEventFormFile = {
129
+ source: `${sourceTemplateDir}/component/component.events.config.hbs`,
130
+ target: getCustomEventFormTarget(routeDir, tileData, changeCase),
131
+ };
132
+
123
133
  const filesToWrite = [componentIndexFile, componentFile, componentStylesFile];
124
134
 
125
135
  if (getTemplateTarget) {
@@ -143,6 +153,9 @@ const getFilesToWrite = (
143
153
  if (tileData.config?.updateFormUiSchema) {
144
154
  filesToWrite.push(componentUpdateFormFile);
145
155
  }
156
+ if (tileData.config?.customEvents) {
157
+ filesToWrite.push(componentCustomEventFormFile);
158
+ }
146
159
  break;
147
160
  case 'grid-pro':
148
161
  if (tileData.config?.gridOptions) {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.3.1](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.3.0...v5.3.1) (2025-08-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * override analytics [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) c568129
9
+ * override analytics [FUI-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#497) f1adb41
10
+
11
+ ## [5.3.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.2.0...v5.3.0) (2025-07-07)
12
+
13
+
14
+ ### Features
15
+
16
+ * advanced custom events (react/web-components) GENC-1002 (#477) baccafb
17
+ * advanced events [PSD-0](https://github.com/genesiscommunitysuccess/blank-app-seed/issues/0) (#483) ba96e02
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * check if custom events exist GENC-1002 (#478) 89d293c
23
+ * show server errors in form GENC-1116 (#479) d6a1291
24
+ * show title in custom events modal GENC-0 (#480) 554f141
25
+
3
26
  ## [5.2.0](https://github.com/genesiscommunitysuccess/blank-app-seed/compare/v5.1.2...v5.2.0) (2025-06-24)
4
27
 
5
28
 
@@ -107,6 +107,7 @@
107
107
  "zone.js": "~0.14.3"
108
108
  },
109
109
  "overrides": {
110
+ "@analytics/core": "0.12.17",
110
111
  "@angular/animations": "^18.0.4",
111
112
  "@angular/common": "^18.0.4",
112
113
  "@angular/compiler": "^18.0.4",
@@ -65,6 +65,9 @@
65
65
  "react-router-dom": "^7.1.3",
66
66
  "web-vitals": "^2.1.4"
67
67
  },
68
+ "overrides": {
69
+ "@analytics/core": "0.12.17"
70
+ },
68
71
  "devDependencies": {
69
72
  "@babel/core": "^7.25.2",
70
73
  "@babel/preset-env": "^7.25.4",
@@ -14,6 +14,7 @@ declare module "react/jsx-runtime" {
14
14
  'rapid-g2plot-chart': CustomElement;
15
15
  'chart-datasource': CustomElement;
16
16
  'client-app-login': CustomElement;
17
+ 'rapid-modal': CustomElement;
17
18
  'rapid-layout': CustomElement;
18
19
  'rapid-layout-region': CustomElement;
19
20
  'rapid-layout-item': CustomElement;
@@ -0,0 +1,174 @@
1
+ import { getConnect } from '@genesislcap/foundation-comms';
2
+ import { showNotification, showNotificationDialog } from '@genesislcap/foundation-notifications';
3
+
4
+ interface ConfirmSubmit {
5
+ state: 'enabled' | 'disabled';
6
+ message: string;
7
+ }
8
+
9
+ interface CustomEventError {
10
+ errors: {
11
+ message?: string;
12
+ CODE: string;
13
+ TEXT: string;
14
+ }[];
15
+ }
16
+
17
+ export interface CustomEventHandler {
18
+ baseEvent: string;
19
+ name: string;
20
+ hasForm: boolean;
21
+ confirmSubmit?: ConfirmSubmit;
22
+ defaultValues?: Record<string, any>;
23
+ }
24
+
25
+ export interface CustomEventState {
26
+ name: string;
27
+ event: string;
28
+ rowData: any;
29
+ }
30
+
31
+ interface RecordTypeValue {
32
+ type: 'record';
33
+ mapping?: string;
34
+ }
35
+
36
+ type DefaultValue = string | number | boolean | null | RecordTypeValue;
37
+
38
+ interface DefaultValues {
39
+ [key: string]: DefaultValue;
40
+ }
41
+
42
+ export const mapDefaultValues = (
43
+ defaultValues: DefaultValues,
44
+ rowData: any
45
+ ): Record<string, any> =>
46
+ Object.entries(defaultValues).reduce(
47
+ (acc, [key, value]) => ({
48
+ ...acc,
49
+ [key]:
50
+ typeof value === 'object' &&
51
+ value !== null &&
52
+ 'type' in value &&
53
+ value.type === 'record'
54
+ ? rowData[(value as RecordTypeValue).mapping || key]
55
+ : value,
56
+ }),
57
+ {}
58
+ );
59
+
60
+ export const executeCustomEvent = async (
61
+ customEventHandler: CustomEventHandler,
62
+ rowData: any
63
+ ): Promise<void> => {
64
+ const payload = customEventHandler.defaultValues
65
+ ? mapDefaultValues(customEventHandler.defaultValues, rowData)
66
+ : {};
67
+
68
+ const res = await getConnect().commitEvent(
69
+ `EVENT_${customEventHandler.baseEvent}`,
70
+ { DETAILS: payload }
71
+ );
72
+
73
+ if (res.MESSAGE_TYPE === 'EVENT_NACK') {
74
+ const err: CustomEventError = {
75
+ errors:
76
+ res?.ERROR?.map((e) => ({
77
+ TEXT: e.TEXT,
78
+ CODE: e.STATUS_CODE ?? '0 Unknown Error',
79
+ message:
80
+ 'PATH' in e && typeof e.PATH === 'string' && 'FIELD' in e && typeof e.FIELD === 'string'
81
+ ? e.TEXT.replace(e.PATH, e.FIELD)
82
+ : undefined,
83
+ })) ?? [],
84
+ };
85
+ submitFailureNotification(new CustomEvent('Error', { detail: err }));
86
+ }
87
+ };
88
+
89
+ export const showCustomEventConfirmation = (
90
+ customEvent: CustomEventHandler,
91
+ onConfirm: () => Promise<void>
92
+ ): void => {
93
+ showNotificationDialog(
94
+ {
95
+ title: customEvent.name,
96
+ body: customEvent.confirmSubmit!.message,
97
+ dialog: {
98
+ confirmingActions: [
99
+ {
100
+ label: 'Confirm',
101
+ action: onConfirm,
102
+ },
103
+ ],
104
+ dismissingAction: {
105
+ label: 'Cancel',
106
+ action: () => {},
107
+ },
108
+ },
109
+ },
110
+ 'rapid',
111
+ );
112
+ };
113
+
114
+ export const submitFailureNotification = (e: CustomEvent<CustomEventError>) => {
115
+ e.detail.errors.forEach((submitFailureError) => {
116
+ if (submitFailureError.CODE === 'OPTIMISTIC_CONCURRENCY_ERROR') {
117
+ showNotification(
118
+ {
119
+ title: 'Warning',
120
+ body: "You're editing an old revision. Please close the form and try editing again",
121
+ config: {
122
+ snackbar: {
123
+ type: 'error',
124
+ },
125
+ },
126
+ },
127
+ 'rapid',
128
+ );
129
+ } else {
130
+ showNotification(
131
+ {
132
+ title: 'Error submitting form',
133
+ body:
134
+ submitFailureError.message ??
135
+ (submitFailureError.CODE + ': ' + submitFailureError.TEXT).toString(),
136
+ config: {
137
+ snackbar: {
138
+ type: 'error',
139
+ },
140
+ },
141
+ },
142
+ 'rapid',
143
+ );
144
+ }
145
+ });
146
+ };
147
+
148
+ export const useCustomEvent =
149
+ (
150
+ customEvent: CustomEventHandler,
151
+ rowData: any,
152
+ setFormData: (data: Record<string, any>) => void,
153
+ setActiveEvent: (event: CustomEventState | null) => void
154
+ ) =>
155
+ async () => {
156
+ if (customEvent.hasForm) {
157
+ const defaultValues = customEvent.defaultValues || {};
158
+ const formData = mapDefaultValues(defaultValues, rowData);
159
+ setFormData(formData);
160
+ setActiveEvent({
161
+ name: customEvent.name,
162
+ event: customEvent.baseEvent,
163
+ rowData,
164
+ });
165
+ } else {
166
+ if (customEvent.confirmSubmit?.state === 'enabled') {
167
+ showCustomEventConfirmation(customEvent, () =>
168
+ executeCustomEvent(customEvent, rowData)
169
+ );
170
+ } else {
171
+ await executeCustomEvent(customEvent, rowData);
172
+ }
173
+ }
174
+ };
@@ -1,5 +1,6 @@
1
+ export * from './customEvents';
1
2
  export * from './fdc3';
3
+ export * from './getLayoutNameByRoute';
2
4
  export * from './layout';
3
5
  export * from './permissions';
4
6
  export * from './setApiHost';
5
- export * from './getLayoutNameByRoute';
@@ -119,6 +119,7 @@
119
119
  "tslib": "^2.3.1"
120
120
  },
121
121
  "overrides": {
122
+ "@analytics/core": "0.12.17",
122
123
  "@genesislcap/foundation-auth": "{{versions.UI}}",
123
124
  "@genesislcap/foundation-comms": "{{versions.UI}}",
124
125
  "@genesislcap/foundation-entity-management": "{{versions.UI}}",
@@ -0,0 +1,168 @@
1
+ import { Connect } from '@genesislcap/foundation-comms';
2
+ import { showNotification, showNotificationDialog } from '@genesislcap/foundation-notifications';
3
+
4
+ interface ConfirmSubmit {
5
+ state: 'enabled' | 'disabled';
6
+ message: string;
7
+ }
8
+
9
+ interface CustomEventError {
10
+ errors: {
11
+ message?: string;
12
+ CODE: string;
13
+ TEXT: string;
14
+ }[];
15
+ }
16
+
17
+ export interface CustomEventHandler {
18
+ baseEvent: string;
19
+ name: string;
20
+ hasForm: boolean;
21
+ confirmSubmit?: ConfirmSubmit;
22
+ defaultValues?: Record<string, any>;
23
+ }
24
+
25
+ export interface CustomEventState {
26
+ name: string;
27
+ event: string;
28
+ rowData: any;
29
+ }
30
+
31
+ interface RecordTypeValue {
32
+ type: 'record';
33
+ mapping?: string;
34
+ }
35
+
36
+ type DefaultValue = string | number | boolean | null | RecordTypeValue;
37
+
38
+ interface DefaultValues {
39
+ [key: string]: DefaultValue;
40
+ }
41
+
42
+ export const mapDefaultValues = (defaultValues: DefaultValues, rowData: any): Record<string, any> =>
43
+ Object.entries(defaultValues).reduce(
44
+ (acc, [key, value]) => ({
45
+ ...acc,
46
+ [key]:
47
+ typeof value === 'object' && value !== null && 'type' in value && value.type === 'record'
48
+ ? rowData[(value as RecordTypeValue).mapping || key]
49
+ : value,
50
+ }),
51
+ {},
52
+ );
53
+
54
+ export const executeCustomEvent = async (
55
+ connect: Connect,
56
+ customEvent: CustomEventHandler,
57
+ rowData: any,
58
+ ): Promise<void> => {
59
+ const payload = customEvent.defaultValues
60
+ ? mapDefaultValues(customEvent.defaultValues, rowData)
61
+ : {};
62
+
63
+ const res = await connect.commitEvent(`EVENT_${customEvent.baseEvent}`, {
64
+ DETAILS: payload,
65
+ });
66
+
67
+ if (res.MESSAGE_TYPE === 'EVENT_NACK') {
68
+ const err: CustomEventError = {
69
+ errors:
70
+ res?.ERROR?.map((e) => ({
71
+ TEXT: e.TEXT,
72
+ CODE: e.STATUS_CODE ?? '0 Unknown Error',
73
+ message:
74
+ 'PATH' in e && typeof e.PATH === 'string' && 'FIELD' in e && typeof e.FIELD === 'string'
75
+ ? e.TEXT.replace(e.PATH, e.FIELD)
76
+ : undefined,
77
+ })) ?? [],
78
+ };
79
+ submitFailureNotification(new CustomEvent('Error', { detail: err }));
80
+ }
81
+ };
82
+
83
+ export const showCustomEventConfirmation = (
84
+ customEvent: CustomEventHandler,
85
+ onConfirm: () => Promise<void>,
86
+ ): void => {
87
+ showNotificationDialog(
88
+ {
89
+ title: customEvent.name,
90
+ body: customEvent.confirmSubmit!.message,
91
+ dialog: {
92
+ confirmingActions: [
93
+ {
94
+ label: 'Confirm',
95
+ action: onConfirm,
96
+ },
97
+ ],
98
+ dismissingAction: {
99
+ label: 'Cancel',
100
+ action: () => {},
101
+ },
102
+ },
103
+ },
104
+ 'rapid',
105
+ );
106
+ };
107
+
108
+ export const submitFailureNotification = (e: CustomEvent<CustomEventError>) => {
109
+ e.detail.errors.forEach((submitFailureError) => {
110
+ if (submitFailureError.CODE === 'OPTIMISTIC_CONCURRENCY_ERROR') {
111
+ showNotification(
112
+ {
113
+ title: 'Warning',
114
+ body: "You're editing an old revision. Please close the form and try editing again",
115
+ config: {
116
+ snackbar: {
117
+ type: 'error',
118
+ },
119
+ },
120
+ },
121
+ 'rapid',
122
+ );
123
+ } else {
124
+ showNotification(
125
+ {
126
+ title: 'Error submitting form',
127
+ body:
128
+ submitFailureError.message ??
129
+ (submitFailureError.CODE + ': ' + submitFailureError.TEXT).toString(),
130
+ config: {
131
+ snackbar: {
132
+ type: 'error',
133
+ },
134
+ },
135
+ },
136
+ 'rapid',
137
+ );
138
+ }
139
+ });
140
+ };
141
+
142
+ export const handleFormCustomEvent = (
143
+ customEvent: CustomEventHandler,
144
+ rowData: any,
145
+ setFormData: (data: Record<string, any>) => void,
146
+ setActiveEvent: (event: CustomEventState) => void,
147
+ showModal: () => void,
148
+ ): void => {
149
+ const defaultValues = customEvent.defaultValues || {};
150
+ const formData = mapDefaultValues(defaultValues, rowData);
151
+
152
+ setFormData(formData);
153
+ setActiveEvent({ name: customEvent.name, event: customEvent.baseEvent, rowData });
154
+ showModal();
155
+ };
156
+
157
+ export const handleNonFormCustomEvent = async (
158
+ connect: Connect,
159
+ customEvent: CustomEventHandler,
160
+ rowData: any,
161
+ showConfirmation: (onConfirm: () => Promise<void>) => void,
162
+ ): Promise<void> => {
163
+ if (customEvent.confirmSubmit?.state === 'enabled') {
164
+ showConfirmation(() => executeCustomEvent(connect, customEvent, rowData));
165
+ } else {
166
+ await executeCustomEvent(connect, customEvent, rowData);
167
+ }
168
+ };
@@ -1,3 +1,4 @@
1
+ export * from './customEvents';
1
2
  export * from './fdc3';
2
3
  export * from './layout';
3
4
  export * from './logger';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/blank-app-seed",
3
3
  "description": "Genesis Blank App Seed",
4
- "version": "5.2.0",
4
+ "version": "5.3.1",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
7
7
  "release": "semantic-release"