namirasoft-account-react 1.4.430 → 1.4.432

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 (138) hide show
  1. package/.env.template +15 -15
  2. package/SKILLS.md +514 -514
  3. package/config-overrides.js +72 -72
  4. package/dist/App.css +32 -0
  5. package/dist/App.d.ts +1 -1
  6. package/dist/NSARouterMaker.js +1 -0
  7. package/dist/NSARouterMaker.js.map +1 -1
  8. package/dist/NSARouterMakerProps.d.ts +2 -0
  9. package/dist/UseParams.d.ts +1 -1
  10. package/dist/components/NSAAccessListDialog.d.ts +2 -2
  11. package/dist/components/NSAAccessListDialog.module.css +71 -71
  12. package/dist/components/NSAMasterMenu.d.ts +1 -1
  13. package/dist/components/NSAMasterMenu.module.css +248 -248
  14. package/dist/components/NSAMasterMenuItem.d.ts +1 -1
  15. package/dist/components/NSAMessageListDialog.d.ts +1 -1
  16. package/dist/components/NSAMessageListDialog.js +4 -3
  17. package/dist/components/NSAMessageListDialog.js.map +1 -1
  18. package/dist/components/NSAMessageListDialog.module.css +99 -99
  19. package/dist/components/NSAProductListDialog.d.ts +1 -1
  20. package/dist/components/NSAProductListDialog.module.css +44 -44
  21. package/dist/components/NSAReorderDialog.d.ts +1 -1
  22. package/dist/components/NSAReorderDialog.module.css +49 -49
  23. package/dist/components/NSASortDialog.d.ts +1 -1
  24. package/dist/components/NSAUserDialog.d.ts +1 -1
  25. package/dist/components/NSAUserDialog.module.css +67 -67
  26. package/dist/components/NSAWorkspaceListDialog.d.ts +1 -1
  27. package/dist/components/NSAWorkspaceListDialog.module.css +41 -41
  28. package/dist/components/NSBoxSecret.d.ts +1 -1
  29. package/dist/components/NSBoxSecret.module.css +23 -23
  30. package/dist/components/NSLabelSecret.d.ts +1 -1
  31. package/dist/components/quickfilter/NSAFilterBoxEnum.module.css +45 -0
  32. package/dist/components/quickfilter/NSAQuickFilterBar.d.ts +1 -1
  33. package/dist/components/quickfilter/NSAQuickFilterBar.module.css +20 -0
  34. package/dist/components/quickfilter/NSAQuickFilterDialog.d.ts +1 -1
  35. package/dist/components/quickfilter/NSAQuickFilterDialog.module.css +75 -0
  36. package/dist/index.css +0 -0
  37. package/dist/layouts/Actions.d.ts +1 -1
  38. package/dist/layouts/Actions.js +8 -6
  39. package/dist/layouts/Actions.js.map +1 -1
  40. package/dist/layouts/NSALayout.d.ts +1 -1
  41. package/dist/layouts/NSALayout.js +4 -1
  42. package/dist/layouts/NSALayout.js.map +1 -1
  43. package/dist/layouts/NSASectionEdit.d.ts +1 -1
  44. package/dist/layouts/NSASectionEditTabPage.d.ts +1 -1
  45. package/dist/layouts/NSASectionEditTabPage.module.css +4 -4
  46. package/dist/layouts/NSASectionList.d.ts +1 -1
  47. package/dist/layouts/NSASectionList.module.css +31 -31
  48. package/dist/layouts/NSASectionView.d.ts +1 -1
  49. package/dist/layouts/NSASectionViewTabMore.d.ts +1 -1
  50. package/dist/layouts/NSASectionViewTabMore.js +3 -3
  51. package/dist/layouts/NSASectionViewTabMore.js.map +1 -1
  52. package/dist/layouts/NSASectionViewTabPage.d.ts +1 -1
  53. package/dist/layouts/NSASectionViewTabPage.module.css +47 -47
  54. package/dist/pages/NSAConsentPage.module.css +26 -26
  55. package/dist/pages/NSAEmailVerificationPage.d.ts +1 -1
  56. package/dist/pages/NSAHomePage.d.ts +1 -1
  57. package/dist/pages/NSAHomePage.module.css +41 -41
  58. package/dist/pages/NSALoginPage.d.ts +1 -1
  59. package/dist/pages/NSALoginPage.module.css +26 -26
  60. package/dist/pages/NSAPhoneVerificationPage.d.ts +1 -1
  61. package/dist/pages/NSAVerificationPage.d.ts +1 -1
  62. package/dist/pages/NSAVerificationPage.module.css +31 -31
  63. package/dist/pages/PaymentRequired.js +4 -2
  64. package/dist/pages/PaymentRequired.js.map +1 -1
  65. package/package.json +94 -94
  66. package/public/index.html +21 -21
  67. package/src/App.css +31 -31
  68. package/src/App.tsx +19 -19
  69. package/src/CTFRow.ts +7 -7
  70. package/src/IEntityInfo.ts +33 -33
  71. package/src/NSACacheService.ts +120 -120
  72. package/src/NSAFilterOperators.ts +92 -92
  73. package/src/NSARouterMaker.tsx +140 -139
  74. package/src/NSARouterMakerConfig.ts +15 -15
  75. package/src/NSARouterMakerProps.ts +24 -22
  76. package/src/Router.tsx +45 -45
  77. package/src/UseParams.tsx +18 -18
  78. package/src/components/NSAAccessListDialog.module.css +71 -71
  79. package/src/components/NSAAccessListDialog.tsx +178 -178
  80. package/src/components/NSAMasterMenu.module.css +248 -248
  81. package/src/components/NSAMasterMenu.tsx +169 -169
  82. package/src/components/NSAMasterMenuItem.tsx +250 -250
  83. package/src/components/NSAMessageListDialog.module.css +99 -99
  84. package/src/components/NSAMessageListDialog.tsx +142 -141
  85. package/src/components/NSAProductListDialog.module.css +44 -44
  86. package/src/components/NSAProductListDialog.tsx +88 -88
  87. package/src/components/NSAReorderDialog.module.css +49 -49
  88. package/src/components/NSAReorderDialog.tsx +149 -149
  89. package/src/components/NSASortDialog.tsx +117 -117
  90. package/src/components/NSATable.tsx +489 -489
  91. package/src/components/NSAUserDialog.module.css +67 -67
  92. package/src/components/NSAUserDialog.tsx +122 -122
  93. package/src/components/NSAWorkspaceListDialog.module.css +41 -41
  94. package/src/components/NSAWorkspaceListDialog.tsx +147 -147
  95. package/src/components/NSBoxSecret.module.css +23 -23
  96. package/src/components/NSBoxSecret.tsx +258 -258
  97. package/src/components/NSLabelSecret.tsx +37 -37
  98. package/src/components/quickfilter/NSAFilterBoxBase.tsx +55 -55
  99. package/src/components/quickfilter/NSAFilterBoxBoolean.tsx +54 -54
  100. package/src/components/quickfilter/NSAFilterBoxDate.tsx +104 -104
  101. package/src/components/quickfilter/NSAFilterBoxDateTime.tsx +105 -105
  102. package/src/components/quickfilter/NSAFilterBoxEnum.module.css +44 -44
  103. package/src/components/quickfilter/NSAFilterBoxEnum.tsx +79 -79
  104. package/src/components/quickfilter/NSAFilterBoxNumber.tsx +136 -136
  105. package/src/components/quickfilter/NSAFilterBoxString.tsx +81 -81
  106. package/src/components/quickfilter/NSAFilterBoxTime.tsx +101 -101
  107. package/src/components/quickfilter/NSAQuickFilterBar.module.css +19 -19
  108. package/src/components/quickfilter/NSAQuickFilterBar.tsx +114 -114
  109. package/src/components/quickfilter/NSAQuickFilterDialog.module.css +74 -74
  110. package/src/components/quickfilter/NSAQuickFilterDialog.tsx +260 -260
  111. package/src/css.d.ts +13 -13
  112. package/src/formatters/SecretFormatter.tsx +24 -24
  113. package/src/index.tsx +24 -24
  114. package/src/layouts/Actions.ts +146 -145
  115. package/src/layouts/CFTUtil.ts +18 -18
  116. package/src/layouts/NSALayout.tsx +844 -841
  117. package/src/layouts/NSASectionEdit.tsx +260 -260
  118. package/src/layouts/NSASectionEditTabPage.module.css +4 -4
  119. package/src/layouts/NSASectionEditTabPage.tsx +250 -250
  120. package/src/layouts/NSASectionList.module.css +31 -31
  121. package/src/layouts/NSASectionList.tsx +149 -149
  122. package/src/layouts/NSASectionView.tsx +100 -100
  123. package/src/layouts/NSASectionViewTabMore.tsx +77 -77
  124. package/src/layouts/NSASectionViewTabPage.module.css +47 -47
  125. package/src/layouts/NSASectionViewTabPage.tsx +419 -419
  126. package/src/main.ts +46 -46
  127. package/src/pages/NSAConsentPage.module.css +26 -26
  128. package/src/pages/NSAConsentPage.tsx +120 -120
  129. package/src/pages/NSAEmailVerificationPage.tsx +30 -30
  130. package/src/pages/NSAHomePage.module.css +41 -41
  131. package/src/pages/NSAHomePage.tsx +102 -102
  132. package/src/pages/NSALoginPage.module.css +26 -26
  133. package/src/pages/NSALoginPage.tsx +87 -87
  134. package/src/pages/NSAPhoneVerificationPage.tsx +94 -94
  135. package/src/pages/NSAVerificationPage.module.css +31 -31
  136. package/src/pages/NSAVerificationPage.tsx +186 -186
  137. package/src/pages/PaymentRequired.tsx +76 -74
  138. package/tsconfig.json +43 -43
@@ -1,842 +1,845 @@
1
- import { NamirasoftAccountClient } from 'namirasoft-account-client';
2
- import { ProductFullRow } from 'namirasoft-api-product';
3
- import { CacheService, CookieService, EnvService, FilterItem, FilterItemOperator, IStorageLocal, NamingConvention } from 'namirasoft-core';
4
- import { CategoryRow, FieldRow, NamirasoftFieldMetaDatabase, NamirasoftFieldServer } from 'namirasoft-field';
5
- import { NamirasoftMessageServer } from 'namirasoft-message';
6
- import { NamirasoftMap } from 'namirasoft-site-map';
7
- import { IBackgroundProps, IBaseComponentProps, IHeaderIconProps, IHeaderRightProps, NSBarActionProps, NSBarAlertProps, NSBarTitleProps, NSButtonRed, NSDialogDelete, NSDialogInfo, NSLayout, NSLoading, NSSpace, NSSpaceSizeType, ProductCacheService } from 'namirasoft-site-react';
8
- import { IHeaderLeftProps } from 'namirasoft-site-react/dist/props/IHeaderLeftProps';
9
- import { Component, createRef } from 'react';
10
- import { Helmet } from 'react-helmet';
11
- import { NSAAccessListDialog } from '../components/NSAAccessListDialog';
12
- import NSAMasterMenu from '../components/NSAMasterMenu';
13
- import { NSAMessageListDialog } from '../components/NSAMessageListDialog';
14
- import { NSAProductListDialog } from '../components/NSAProductListDialog';
15
- import { NSAUserDialog } from '../components/NSAUserDialog';
16
- import { NSAWorkspaceListDialog } from '../components/NSAWorkspaceListDialog';
17
- import { IEntityInfo } from '../IEntityInfo';
18
- import { NSACacheService } from '../NSACacheService';
19
- import { NSARouterMakerProps } from '../NSARouterMakerProps';
20
- import { UseParams } from '../UseParams';
21
- import { Actions } from './Actions';
22
- import { NSASectionEdit, NSASectionEditCustomProps } from './NSASectionEdit';
23
- import { NSASectionList, NSASectionListCustomProps } from './NSASectionList';
24
- import { NSASectionView, NSASectionViewCutomProps } from './NSASectionView';
25
- import NSProgressBar from 'namirasoft-site-react/dist/components/NSProgressBar';
26
-
27
- let REACT_APP_BASE_URL_ACCOUNT_CONSOLE = new EnvService("REACT_APP_BASE_URL_ACCOUNT_CONSOLE").getString();
28
- let client = new NamirasoftAccountClient(REACT_APP_BASE_URL_ACCOUNT_CONSOLE);
29
- client.onLoggedin = () => { window.location.reload(); };
30
- client.onLoggedout = () => { window.location.reload(); };
31
-
32
- export interface NSALayoutPagesProps<RowType extends { id: string }, RowTypeInput = RowType>
33
- {
34
- entity: IEntityInfo<RowType, RowTypeInput>;
35
- ui?: {
36
- action?: {
37
- menus: {
38
- _new?: boolean;
39
- list?: boolean;
40
- view?: boolean;
41
- view_history?: boolean;
42
- copy?: boolean;
43
- edit?: boolean;
44
- delete?: boolean;
45
- apply?: boolean;
46
- };
47
- children?: JSX.Element | JSX.Element[];
48
- };
49
- }
50
- list?: NSASectionListCustomProps<RowType, RowTypeInput>;
51
- edit?: NSASectionEditCustomProps<RowType, RowTypeInput> & {
52
- ui: {
53
- action?: {
54
- menus: {
55
- apply?: boolean,
56
- }
57
- },
58
- }
59
- };
60
- view?: NSASectionViewCutomProps<RowType>;
61
- }
62
- export interface NSALayoutCustomProps<RowType extends { id: string }, RowTypeInput = RowType> extends IBaseComponentProps, NSARouterMakerProps
63
- {
64
- action?: NSBarActionProps;
65
- title?: NSBarTitleProps;
66
- alerts?: NSBarAlertProps[];
67
- pages?: NSALayoutPagesProps<RowType, RowTypeInput>;
68
- children: JSX.Element | JSX.Element[];
69
- onEnterPressed?: () => void;
70
- onDidMounted?: () => void;
71
- }
72
- export interface NSALayoutProps<RowType extends { id: string }, RowTypeInput = RowType> extends NSALayoutCustomProps<RowType, RowTypeInput>
73
- {
74
- model?: {
75
- id: string | null;
76
- }
77
- scope?: string;
78
- logo?: string;
79
- master_menu?: boolean;
80
- background?: IBackgroundProps;
81
- header?: {
82
- enabled?: boolean,
83
- right?: IHeaderRightProps;
84
- left?: IHeaderLeftProps;
85
- built_in?: {
86
- logout?: boolean;
87
- message?: boolean;
88
- access?: boolean;
89
- workspace?: boolean;
90
- product?: boolean;
91
- user?: boolean;
92
- };
93
- mega_menu?: boolean;
94
- };
95
- }
96
-
97
- interface NSALayoutState
98
- {
99
- parmas: any | null;
100
- product: ProductFullRow | null;
101
- refresher: number;
102
- message_count: number;
103
- showDeleteDialog: boolean;
104
- deleting: boolean;
105
- deleteTotal: number;
106
- deleteDone: number;
107
- deleteFailed: number;
108
- showMessageListDialog: boolean;
109
- showAccessListDialog: boolean;
110
- showProductListDialog: boolean;
111
- showWorkspaceListDialog: boolean;
112
- showUserDialog: boolean;
113
- showMasterMenu: boolean;
114
- ctf_enabled: boolean;
115
- ctf_categories: CategoryRow[] | null;
116
- ctf_fields: FieldRow[] | null;
117
- }
118
-
119
- export class NSALayout<RowType extends { id: string }, RowTypeInput = RowType> extends Component<NSALayoutProps<RowType, RowTypeInput>, NSALayoutState>
120
- {
121
- private onDidMounted_Called: boolean = false;
122
- private onDidMounted_Ready: boolean = false;
123
- private hasCFT: boolean;
124
- private isLoggedIn: boolean;
125
- private cache_product: CacheService<ProductFullRow>;
126
- private cache_fields: CacheService<{
127
- ctf_categories: CategoryRow[] | null;
128
- ctf_fields: FieldRow[] | null;
129
- }>;
130
- private cache_message: CacheService<number>;
131
- private cache_master_menu: CacheService<{ open: boolean }>;
132
- NSASectionList_Ref = createRef<NSASectionList<RowType, RowTypeInput>>();
133
- NSASectionEdit_Ref = createRef<NSASectionEdit<RowType, RowTypeInput>>();
134
- NSASectionView_Ref = createRef<NSASectionView<RowType, RowTypeInput>>();
135
- private cancelDelete: boolean = false;
136
-
137
- constructor(props: NSALayoutProps<RowType, RowTypeInput>)
138
- {
139
- super(props);
140
-
141
- // Account
142
- this.isLoggedIn = this.props.account?.token_manager?.exists() ?? false;
143
-
144
- // CFT
145
- this.hasCFT = false;
146
- if (this.props.pages?.entity)
147
- try
148
- {
149
- let namirasoft = new NamirasoftMap(this.props.account.token_manager, props.notifier.onError);
150
- let table = namirasoft.getByProductID(this.props.pages.entity.product_id).tables[this.props.pages.entity.name];
151
- this.hasCFT = table.hasFieldTables();
152
- } catch (error)
153
- { }
154
-
155
- // Cache Product
156
- this.cache_product = ProductCacheService.get(this.props.product_id, console.error);
157
-
158
- // Cache Fields
159
- // todo this must be owner_id, otherwise switching between owners does not work correctly
160
- let user_id = this.props.account.token_manager.getUserData(x => x.id, "");
161
- let key_field = `ns-fields-${user_id}-${props.pages?.entity?.product_id ?? "*"}-${props.pages?.entity?.name ?? "*"}`;
162
- this.cache_fields = new CacheService<{
163
- ctf_categories: CategoryRow[] | null;
164
- ctf_fields: FieldRow[] | null;
165
- }>(key_field, new IStorageLocal(), 24 * 60, async () =>
166
- {
167
- let service = new CookieService(document.cookie, "ns-field-version");
168
- return (this.props.account?.token_manager?.getUserData(x => x.id, "") ?? "") + "" + service.getString();
169
- }, async () =>
170
- {
171
- let REACT_APP_BASE_URL_FIELD = new EnvService("REACT_APP_BASE_URL_FIELD", true).getString();
172
- let server = new NamirasoftFieldServer(REACT_APP_BASE_URL_FIELD, props.account.token_manager, props.notifier.onError);
173
- let ans: {
174
- ctf_categories: CategoryRow[] | null;
175
- ctf_fields: FieldRow[] | null;
176
- } = { ctf_categories: null, ctf_fields: null };
177
- if (this.props.pages)
178
- {
179
- let meta = new NamirasoftFieldMetaDatabase();
180
- try
181
- {
182
- let filters: FilterItem[] = [];
183
- filters.push(new FilterItem(meta.tables.category, meta.tables.category.columns.product_id, false, FilterItemOperator.all.equals, this.props.pages.entity.product_id));
184
- filters.push(new FilterItem(meta.tables.category, meta.tables.category.columns.entity, false, FilterItemOperator.all.equals, this.props.pages.entity.name));
185
- let res = await server.category.List(filters, null, null, []);
186
- ans.ctf_categories = res.rows;
187
- } catch (error)
188
- {
189
- ans.ctf_categories = [];
190
- }
191
- try
192
- {
193
- let filters: FilterItem[] = [];
194
- filters.push(new FilterItem(meta.tables.field, meta.tables.field.columns.product_id, false, FilterItemOperator.all.equals, this.props.pages.entity.product_id));
195
- filters.push(new FilterItem(meta.tables.field, meta.tables.field.columns.entity, false, FilterItemOperator.all.equals, this.props.pages.entity.name));
196
- let res = await server.field.List(filters, null, null, []);
197
- ans.ctf_fields = res.rows;
198
- } catch (error)
199
- {
200
- ans.ctf_fields = [];
201
- }
202
- }
203
- return ans;
204
- });
205
- // Cache Message
206
- this.cache_message = new CacheService<number>("ns-messsage-count", new IStorageLocal(), 15, async () => "1.0.0", async () =>
207
- {
208
- let REACT_APP_BASE_URL_MESSAGE = new EnvService("REACT_APP_BASE_URL_MESSAGE", true).getString();
209
- let server = new NamirasoftMessageServer(REACT_APP_BASE_URL_MESSAGE, this.props.account.token_manager, console.error);
210
- let res = await server.message.GetNewCount();
211
- return res.count;
212
- });
213
-
214
- this.cache_master_menu = NSACacheService.getMasterMenuStatus();
215
- this.cache_message.onExpired = () => { this.reloadMessageCount(true); };
216
-
217
- // State
218
- this.state = {
219
- parmas: null,
220
- product: null,
221
- refresher: 0,
222
- message_count: 0,
223
- showDeleteDialog: false,
224
- deleting: false,
225
- deleteTotal: 0,
226
- deleteDone: 0,
227
- deleteFailed: 0,
228
- showMessageListDialog: false,
229
- showAccessListDialog: false,
230
- showProductListDialog: false,
231
- showWorkspaceListDialog: false,
232
- showUserDialog: false,
233
- showMasterMenu: false,
234
- ctf_enabled: this.hasCFT,
235
- ctf_fields: null,
236
- ctf_categories: null,
237
- };
238
- }
239
- public reloadMessageCount(force: boolean)
240
- {
241
- if (force)
242
- this.cache_message.del();
243
- this.cache_message.get().then(count =>
244
- {
245
- this.setState(prev => ({ ...prev, message_count: count }));
246
- }).catch(() => { });
247
- }
248
-
249
- private async runDelete(ids: string[], name: string): Promise<void>
250
- {
251
- const del = this.props.pages?.entity.server.delete;
252
- if (!del)
253
- return;
254
-
255
- this.cancelDelete = false;
256
- this.setState(prev => ({ ...prev, deleting: true, deleteTotal: ids.length, deleteDone: 0, deleteFailed: 0 }));
257
-
258
- const deletedIds: string[] = [];
259
- let failed = 0;
260
- for (const id of ids)
261
- {
262
- if (this.cancelDelete)
263
- break;
264
- try
265
- {
266
- await del(id);
267
- deletedIds.push(id);
268
- this.setState(prev => ({ ...prev, deleteDone: prev.deleteDone + 1 }));
269
- }
270
- catch (error: any)
271
- {
272
- failed++;
273
- this.setState(prev => ({ ...prev, deleteFailed: prev.deleteFailed + 1 }));
274
- }
275
- }
276
-
277
- const cancelled = this.cancelDelete;
278
- const remaining = ids.length - deletedIds.length - failed;
279
-
280
- if (deletedIds.length > 0)
281
- this.props.notifier.onSuccess(
282
- deletedIds.length === 1
283
- ? `${name} was deleted successfully.`
284
- : `${deletedIds.length} ${name}s were deleted successfully.`
285
- );
286
- if (failed > 0)
287
- this.props.notifier.onError(new Error(`Failed to delete ${failed} ${name}${failed > 1 ? "s" : ""}.`));
288
- if (cancelled && remaining > 0)
289
- this.props.notifier.onWarning(`Deletion cancelled. ${remaining} ${name}${remaining > 1 ? "s" : ""} not deleted.`);
290
-
291
- if (deletedIds.length > 0)
292
- {
293
- if (this.props.pages?.list)
294
- {
295
- let table = this.NSASectionList_Ref.current?.NSATable_Main.current;
296
- if (table)
297
- {
298
- let selected_ids = table.getSelectedIDs().filter(id => !deletedIds.includes(id));
299
- let selected_rows = table.getSelectedRows().filter(row => !deletedIds.includes(row.id));
300
- table.setSelectedIDs(selected_ids, selected_rows);
301
- table.reload(null, null);
302
- }
303
- }
304
- else if (this.props.pages?.edit)
305
- {
306
- if (this.props.pages.entity.client.getListURL)
307
- this.props.url.redirect(this.props.pages.entity.client.getListURL(), {});
308
- else if (this.props.pages.entity.client.getViewURL)
309
- this.props.url.redirect(this.props.pages.entity.client.getViewURL(deletedIds[0]), {});
310
- }
311
- else if (this.props.pages?.view)
312
- {
313
- if (this.props.pages.entity.client.getListURL)
314
- this.props.url.redirect(this.props.pages.entity.client.getListURL(), {});
315
- }
316
- }
317
-
318
- this.setState(prev => ({ ...prev, deleting: false, showDeleteDialog: false }));
319
- }
320
-
321
- override componentDidMount(): void
322
- {
323
- this.onDidMounted_Called = false;
324
- this.cache_product.get().then(product =>
325
- {
326
- this.setState(prev => ({ ...prev, product }));
327
- })
328
-
329
- this.cache_master_menu.get().then(status =>
330
- {
331
- this.setState(() => ({ showMasterMenu: status.open }));
332
- })
333
-
334
- if (this.hasCFT)
335
- if (this.props.pages?.entity)
336
- {
337
- this.cache_fields.get().then(res =>
338
- {
339
- this.setState(prev => { return { ...prev, ctf_categories: res.ctf_categories ?? [], ctf_fields: res.ctf_fields ?? [] } });
340
- });
341
- }
342
-
343
- if (this.isLoggedIn)
344
- this.reloadMessageCount(false);
345
-
346
- client.start();
347
- }
348
- override componentDidUpdate(): void
349
- {
350
- if (!this.onDidMounted_Called)
351
- if (this.onDidMounted_Ready)
352
- {
353
- this.props.onDidMounted?.();
354
- this.onDidMounted_Called = true;
355
- }
356
- }
357
- override componentWillUnmount(): void
358
- {
359
- client.stop();
360
- }
361
- override render()
362
- {
363
- this.onDidMounted_Ready = false;
364
- let id = this.props.model?.id ?? null;
365
- if (!id)
366
- {
367
- if (this.state.parmas === null)
368
- return <UseParams onLoaded={parmas =>
369
- {
370
- this.setState({ parmas });
371
- }} />;
372
- id = this.state.parmas.id;
373
- }
374
-
375
- const hideDeleteDialog = async () => { this.setState(prev => ({ ...prev, showDeleteDialog: false })); };
376
- const showDeleteDialog = async () => { this.setState(prev => ({ ...prev, showDeleteDialog: true })); };
377
-
378
- let icons: IHeaderIconProps[] = [];
379
-
380
- if (this.isLoggedIn)
381
- {
382
- if (this.props.header?.built_in?.message ?? true)
383
- icons.push({
384
- src: "https://static.namirasoft.com/image/namirasoft/message/logo/base.png", alt: "Message", onClicked: () =>
385
- {
386
- this.setState(prev => ({ ...prev, showMessageListDialog: true }));
387
- this.reloadMessageCount(true);
388
- },
389
- children: <>
390
- {
391
- (
392
- this.state.showMessageListDialog &&
393
- <NSAMessageListDialog {...this.props}
394
- onClose={() => { this.setState(prev => ({ ...prev, showMessageListDialog: false })); }}
395
- onSeenAll={() =>
396
- {
397
- this.reloadMessageCount(true);
398
- }}
399
- onMessageClick={() =>
400
- {
401
- this.reloadMessageCount(true);
402
- }}
403
- />
404
- )
405
- }
406
- </>,
407
- count: this.state.message_count,
408
- tooltip: "Message"
409
- });
410
- }
411
- if (this.isLoggedIn)
412
- if (this.props.header?.built_in?.access ?? true)
413
- icons.push({
414
- src: "https://static.namirasoft.com/image/namirasoft/access/logo/base.png", alt: "Access", onClicked: () =>
415
- {
416
- this.setState(prev => ({ ...prev, showAccessListDialog: true }));
417
- },
418
- children: <>
419
- {
420
- (this.state.showAccessListDialog) && <NSAAccessListDialog {...this.props} onClose={() =>
421
- {
422
- this.setState(prev => ({ ...prev, showAccessListDialog: false }));
423
- }} />
424
- }
425
- </>,
426
- tooltip: "Access"
427
- });
428
- if (this.isLoggedIn)
429
- if (this.props.header?.built_in?.workspace ?? true)
430
- icons.push({
431
- src: "https://static.namirasoft.com/image/namirasoft/workspace/logo/base.png", alt: "Workspace", onClicked: () =>
432
- {
433
- this.setState(prev => ({ ...prev, showWorkspaceListDialog: true }));
434
- },
435
- children: <>
436
- {
437
- (this.state.showWorkspaceListDialog) && <NSAWorkspaceListDialog {...this.props} onClose={() =>
438
- {
439
- this.setState(prev => ({ ...prev, showWorkspaceListDialog: false }));
440
- }} />
441
- }
442
- </>,
443
- tooltip: "Workspace List"
444
- });
445
- if (this.props.header?.built_in?.product ?? true)
446
- icons.push({
447
- src: "https://static.namirasoft.com/image/concept/product-list/white.svg", alt: "Product", onClicked: () =>
448
- {
449
- this.setState(prev => ({ ...prev, showProductListDialog: true }));
450
- },
451
- children: <>
452
- {
453
- (this.state.showProductListDialog) && <NSAProductListDialog {...this.props} onClose={() =>
454
- {
455
- this.setState(prev => ({ ...prev, showProductListDialog: false }));
456
- }} >
457
- <></>
458
- </NSAProductListDialog>
459
- }
460
- </>,
461
- tooltip: "Product List"
462
- });
463
-
464
- if (this.isLoggedIn)
465
- if (this.props.header?.built_in?.user ?? true)
466
- icons.push({
467
- src: "https://static.namirasoft.com/image/concept/user/white.svg",
468
- alt: "User",
469
- onClicked: () =>
470
- {
471
- this.setState(prev => ({ ...prev, showUserDialog: true }));
472
- },
473
- children: <>
474
- {
475
- (this.state.showUserDialog) &&
476
- <NSAUserDialog
477
- {...this.props}
478
- client={client}
479
- onClose={() => this.setState(prev => ({ ...prev, showUserDialog: false }))}
480
- >
481
- <></>
482
- </NSAUserDialog>
483
- }
484
- </>,
485
- tooltip: "User"
486
- });
487
-
488
- let name = "";
489
- if (this.props.pages)
490
- name = NamingConvention.lower_case_underscore.convert(this.props.pages.entity.name, NamingConvention.Pascal_Case_Space);
491
-
492
- let getIDs: (() => string[]) | null = null;
493
- let getRows: (() => RowType[]) | null = null;
494
- if (this.props.pages?.list)
495
- {
496
- getIDs = () => { return this.NSASectionList_Ref.current?.getSelectedIDs() ?? []; };
497
- getRows = () => { return this.NSASectionList_Ref.current?.getSelectedRows() ?? []; };
498
- }
499
- if (this.props.pages?.edit)
500
- {
501
- getIDs = () => [id].filter(id => id) as string[];
502
- getRows = () => [this.NSASectionEdit_Ref.current?.getRow()].filter(row => row) as RowType[];
503
- }
504
- if (this.props.pages?.view)
505
- {
506
- getIDs = () => [id].filter(id => id) as string[];
507
- getRows = () => [this.NSASectionView_Ref.current?.getRow()].filter(row => row) as RowType[];
508
- }
509
-
510
- let action: NSBarActionProps | undefined = { title: "", description: "", menus: {} };
511
- if (this.props.action?.title)
512
- action.title = this.props.action.title;
513
- if (this.props.action?.description)
514
- action.description = this.props.action.description;
515
- if (this.props.action?.menus != null)
516
- action.menus = { ...this.props.action?.menus };
517
-
518
- if (getIDs && getRows)
519
- if (this.props.pages?.entity.client.getMenus)
520
- {
521
- let menus = this.props.pages?.entity.client.getMenus(getIDs, getRows);
522
- for (let key of Object.keys(menus))
523
- {
524
- if (!action.menus)
525
- action.menus = {};
526
- if (!action.menus[key])
527
- action.menus[key] = [];
528
- action.menus[key].push(...menus[key]);
529
- }
530
- }
531
-
532
- let name_apply = "Apply";
533
- let name_actions = "Actions";
534
- if (!action.menus)
535
- action.menus = {};
536
- action.menus[name_apply] = [];
537
- action.menus[name_actions] = [];
538
-
539
- if (this.props.pages?.edit)
540
- {
541
- if (!action.title)
542
- action.title = (this.props.pages.edit.isEdit ? "Edit" : "New") + " " + name;
543
-
544
- // Menu
545
- if (this.props.pages.edit.ui.action?.menus.apply ?? true)
546
- action.menus[name_apply].push(Actions.apply({
547
- action: (onFinished) =>
548
- {
549
- if (this.props.pages?.edit)
550
- this.NSASectionEdit_Ref.current?.onApply(onFinished);
551
- }
552
- }));
553
- }
554
- else if (this.props.pages)
555
- {
556
- if (this.props.pages.entity.client.getNewURL)
557
- if (this.props.pages.entity.server.create)
558
- if (this.props.pages.ui?.action?.menus._new ?? true)
559
- action.menus[name_apply].push(Actions._new(this.props, this.props.pages.entity));
560
- }
561
-
562
- if (getIDs)
563
- if (this.props.pages)
564
- {
565
- if (this.props.pages.entity.client.getListURL)
566
- if (this.props.pages.entity.server.list)
567
- if (this.props.pages.ui?.action?.menus.list ?? true)
568
- action.menus[name_actions].push(Actions.list(this.props, this.props.pages.entity));
569
-
570
- if (this.props.pages.entity.client.getViewURL)
571
- if (this.props.pages.entity.server.get)
572
- if (this.props.pages.ui?.action?.menus.view ?? true)
573
- action.menus[name_actions].push(Actions.view(this.props, this.props.pages.entity, getIDs));
574
-
575
- if (this.props.pages.ui?.action?.menus.view_history ?? true)
576
- action.menus[name_actions].push(Actions.viewHistory(getIDs));
577
-
578
- if (this.props.pages.entity.client.getCopyURL)
579
- if (this.props.pages.entity.server.create)
580
- if (this.props.pages.ui?.action?.menus.copy ?? true)
581
- action.menus[name_actions].push(Actions.copy(this.props, this.props.pages.entity, getIDs));
582
-
583
- if (this.props.pages.entity.client.getEditURL)
584
- if (this.props.pages.entity.server.update)
585
- if (this.props.pages.ui?.action?.menus.edit ?? true)
586
- action.menus[name_actions].push(Actions.edit(this.props, this.props.pages.entity, getIDs));
587
-
588
- if (this.props.pages.entity.server.delete)
589
- if (this.props.pages.ui?.action?.menus.delete ?? true)
590
- action.menus[name_actions].push(Actions.delete(getIDs, {
591
- action: showDeleteDialog,
592
- showLoading: false
593
- }));
594
- }
595
-
596
- let action_name = "";
597
- let content;
598
- if (this.props.pages?.list)
599
- {
600
- action_name = "List";
601
- content = <NSASectionList<RowType, RowTypeInput>
602
- ref={this.NSASectionList_Ref}
603
- {...this.props}
604
- {...this.props.pages.list}
605
- entity={this.props.pages.entity}
606
- onIDsChanged={() =>
607
- {
608
- this.setState(prev => ({ ...prev }));
609
- this.props.pages?.list?.onIDsChanged?.();
610
- }}
611
- ctf={{
612
- enabled: this.state.ctf_enabled,
613
- categories: this.state.ctf_categories,
614
- fields: this.state.ctf_fields
615
- }}
616
- deleteFieldCache={() => { this.cache_fields.del(); }}
617
- >
618
- {this.props.children}
619
- </NSASectionList>
620
- }
621
- else if (this.props.pages?.edit)
622
- {
623
- if (this.props.pages.edit.isEdit)
624
- action_name = "Edit";
625
- else
626
- action_name = "New";
627
- content = <>
628
- <NSASectionEdit
629
- ref={this.NSASectionEdit_Ref}
630
- {...this.props}
631
- {...this.props.pages.edit}
632
- entity={this.props.pages.entity}
633
- ctf={{
634
- enabled: this.state.ctf_enabled,
635
- categories: this.state.ctf_categories,
636
- fields: this.state.ctf_fields
637
- }}
638
- deleteFieldCache={() => { this.cache_fields.del(); }}
639
- onRowLoaded={(row) =>
640
- {
641
- this.setState(prev => ({ ...prev }), () =>
642
- {
643
- this.props.pages?.edit?.onRowLoaded?.(row);
644
- });
645
- }}
646
- model={{
647
- id,
648
- row: null
649
- }}
650
- >
651
- {this.props.children}
652
- </NSASectionEdit>
653
- </>;
654
- }
655
- else if (this.props.pages?.view)
656
- {
657
- action_name = "View";
658
- content = <NSASectionView
659
- ref={this.NSASectionView_Ref}
660
- {...this.props}
661
- {...this.props.pages.view}
662
- entity={this.props.pages.entity}
663
- ctf={{
664
- enabled: this.state.ctf_enabled,
665
- categories: this.state.ctf_categories,
666
- fields: this.state.ctf_fields
667
- }}
668
- deleteFieldCache={() => { this.cache_fields.del(); }}
669
- onRowLoaded={(row) =>
670
- {
671
- this.setState(prev => ({ ...prev }), () =>
672
- {
673
- this.props.pages?.view?.onRowLoaded?.(row);
674
- });
675
- }}
676
- model={{
677
- id,
678
- row: null
679
- }}
680
- >
681
- {this.props.children}
682
- </NSASectionView>
683
- }
684
- else
685
- content = this.props.children;
686
-
687
- let fullname = "";
688
- if (this.props.pages?.entity.name)
689
- {
690
- let Name_Pascal_Space = NamingConvention.lower_case_underscore.convert(this.props.pages.entity.name, NamingConvention.Pascal_Case_Space);
691
- if (action_name === "List")
692
- fullname = Name_Pascal_Space + " " + action_name;
693
- else
694
- fullname = action_name + " " + Name_Pascal_Space;
695
- }
696
-
697
- if (!this.state.product)
698
- return <NSLoading />;
699
-
700
- let action_title = action?.title ?? "";
701
- if (!action_title)
702
- action_title = fullname;
703
- if (!action_title)
704
- action_title = this.state.product?.name
705
-
706
- let action_description = action?.description ?? "";
707
- if (!action_description)
708
- action_description = this.state.product?.description ?? "";
709
-
710
- let scope = this.props.scope ?? `${this.state.product.name} Console`;
711
- let logo = this.props.logo ?? this.state.product.logo ?? "";
712
- let background = this.props.background?.image ?? this.state.product.background ?? "";
713
-
714
- this.onDidMounted_Ready = true;
715
-
716
- let user_id = this.props.account.token_manager.getUserData(x => x.id, "");
717
- let user_first_name = this.props.account.token_manager.getUserData(x => x.first_name, "");
718
- let user_last_name = this.props.account.token_manager.getUserData(x => x.last_name, "");
719
- let user_email = this.props.account.token_manager.getUserData(x => x.email, "");
720
- let user_phone = this.props.account.token_manager.getUserData(x => x.phone ?? "", "");
721
- return (
722
- <div className="d-flex">
723
- {
724
- (this.props.master_menu ?? true) &&
725
- <NSAMasterMenu
726
- {...this.props}
727
- open={this.state.showMasterMenu}
728
- setOpen={(open) =>
729
- {
730
- this.cache_master_menu.set({ open });
731
- this.setState(() => ({ showMasterMenu: open }));
732
- }}
733
- />
734
- }
735
- <NSLayout
736
- {...this.props}
737
- scope={scope}
738
- logo={logo}
739
- background={{ image: background, attachment: this.props.background?.attachment, size: this.props.background?.size }}
740
- header={{
741
- enabled: this.props.header?.enabled,
742
- right: {
743
- icons: [...icons, ...(this.props.header?.right?.icons ?? [])],
744
- },
745
- left: {
746
- title: this.props.header?.left?.title
747
- },
748
- mega_menu: this.props.header?.mega_menu,
749
- onCollapse: () =>
750
- {
751
- this.setState((prev) => ({ showMasterMenu: !prev.showMasterMenu }))
752
- },
753
- }}
754
- action={{
755
- title: action_title,
756
- description: action_description,
757
- menus: action.menus,
758
- children: this.props.action?.children
759
- }}
760
- isLoggedIn={this.props.account?.token_manager?.exists}
761
- notifications={this.props.notifier.getNotifications()}
762
- >
763
- <Helmet>
764
- <meta name="ns-account-user-id" content={user_id} />
765
- <meta name="ns-account-user-first-name" content={user_first_name} />
766
- <meta name="ns-account-user-last-name" content={user_last_name} />
767
- <meta name="ns-account-user-email" content={user_email} />
768
- <meta name="ns-account-user-phone" content={user_phone} />
769
- </Helmet>
770
- {
771
- this.state.showDeleteDialog && !this.state.deleting &&
772
- <NSDialogDelete
773
- title={"Delete Confirmation"}
774
- description={
775
- (getIDs && getIDs().length > 1)
776
- ? `Are you sure you want to delete ${getIDs().length} ${name}s?`
777
- : `Are you sure you want to delete ${name}?`
778
- }
779
- onClose={hideDeleteDialog}
780
- onNo={{ action: hideDeleteDialog, showLoading: false }}
781
- show_as_code={false}
782
- onYes={{
783
- showLoading: false,
784
- action: async (onFinished) =>
785
- {
786
- try
787
- {
788
- let ids = getIDs ? getIDs() : [];
789
- if (ids.length === 0)
790
- throw new Error("Nothing was found to delete.");
791
- await this.runDelete(ids, name);
792
- }
793
- catch (error: any)
794
- {
795
- this.props.notifier.onError(error);
796
- this.setState(prev => ({ ...prev, showDeleteDialog: false }));
797
- }
798
- onFinished();
799
- }
800
- }}
801
- >
802
- <></>
803
- </NSDialogDelete>}
804
- {
805
- this.state.showDeleteDialog && this.state.deleting &&
806
- <NSDialogInfo
807
- title={"Deleting"}
808
- show_as_code={false}
809
- onClose={() => { }}
810
- closeButton={{ show: false }}
811
- >
812
- <NSProgressBar
813
- progress={this.state.deleteTotal > 0
814
- ? ((this.state.deleteDone + this.state.deleteFailed) / this.state.deleteTotal) * 100
815
- : 0}
816
- title={`Deleting... [${this.state.deleteDone + this.state.deleteFailed}/${this.state.deleteTotal}]`}
817
- />
818
- <NSSpace size={NSSpaceSizeType.MINI} />
819
- <NSButtonRed
820
- title="Cancel"
821
- onClick={{
822
- showLoading: false,
823
- action: (onFinished) =>
824
- {
825
- this.cancelDelete = true;
826
- onFinished();
827
- }
828
- }}
829
- />
830
- </NSDialogInfo>}
831
- {
832
- (!this.state.ctf_enabled || (this.state.ctf_fields && this.state.ctf_categories))
833
- ?
834
- content
835
- :
836
- <NSLoading />
837
- }
838
- </NSLayout>
839
- </div >
840
- );
841
- }
1
+ import { NamirasoftAccountClient } from 'namirasoft-account-client';
2
+ import { ProductFullRow } from 'namirasoft-api-product';
3
+ import { CacheService, CookieService, EnvService, FilterItem, FilterItemOperator, IStorageLocal, NamingConvention } from 'namirasoft-core';
4
+ import { CategoryRow, FieldRow, NamirasoftFieldMetaDatabase, NamirasoftFieldServer } from 'namirasoft-field';
5
+ import { NamirasoftMessageServer } from 'namirasoft-message';
6
+ import { NamirasoftMap } from 'namirasoft-site-map';
7
+ import { IBackgroundProps, IBaseComponentProps, IHeaderIconProps, IHeaderRightProps, KeyOperation, LinkOperation, NSBarActionProps, NSBarAlertProps, NSBarTitleProps, NSButtonRed, NSDialogDelete, NSDialogInfo, NSLayout, NSLoading, NSSpace, NSSpaceSizeType, ProductCacheService } from 'namirasoft-site-react';
8
+ import { IHeaderLeftProps } from 'namirasoft-site-react/dist/props/IHeaderLeftProps';
9
+ import { Component, createRef } from 'react';
10
+ import { Helmet } from 'react-helmet';
11
+ import { NSAAccessListDialog } from '../components/NSAAccessListDialog';
12
+ import NSAMasterMenu from '../components/NSAMasterMenu';
13
+ import { NSAMessageListDialog } from '../components/NSAMessageListDialog';
14
+ import { NSAProductListDialog } from '../components/NSAProductListDialog';
15
+ import { NSAUserDialog } from '../components/NSAUserDialog';
16
+ import { NSAWorkspaceListDialog } from '../components/NSAWorkspaceListDialog';
17
+ import { IEntityInfo } from '../IEntityInfo';
18
+ import { NSACacheService } from '../NSACacheService';
19
+ import { NSARouterMakerProps } from '../NSARouterMakerProps';
20
+ import { UseParams } from '../UseParams';
21
+ import { Actions } from './Actions';
22
+ import { NSASectionEdit, NSASectionEditCustomProps } from './NSASectionEdit';
23
+ import { NSASectionList, NSASectionListCustomProps } from './NSASectionList';
24
+ import { NSASectionView, NSASectionViewCutomProps } from './NSASectionView';
25
+ import NSProgressBar from 'namirasoft-site-react/dist/components/NSProgressBar';
26
+
27
+ let REACT_APP_BASE_URL_ACCOUNT_CONSOLE = new EnvService("REACT_APP_BASE_URL_ACCOUNT_CONSOLE").getString();
28
+ let client = new NamirasoftAccountClient(REACT_APP_BASE_URL_ACCOUNT_CONSOLE);
29
+ client.onLoggedin = () => { window.location.reload(); };
30
+ client.onLoggedout = () => { window.location.reload(); };
31
+
32
+ export interface NSALayoutPagesProps<RowType extends { id: string }, RowTypeInput = RowType>
33
+ {
34
+ entity: IEntityInfo<RowType, RowTypeInput>;
35
+ ui?: {
36
+ action?: {
37
+ menus: {
38
+ _new?: boolean;
39
+ list?: boolean;
40
+ view?: boolean;
41
+ view_history?: boolean;
42
+ copy?: boolean;
43
+ edit?: boolean;
44
+ delete?: boolean;
45
+ apply?: boolean;
46
+ };
47
+ children?: JSX.Element | JSX.Element[];
48
+ };
49
+ }
50
+ list?: NSASectionListCustomProps<RowType, RowTypeInput>;
51
+ edit?: NSASectionEditCustomProps<RowType, RowTypeInput> & {
52
+ ui: {
53
+ action?: {
54
+ menus: {
55
+ apply?: boolean,
56
+ }
57
+ },
58
+ }
59
+ };
60
+ view?: NSASectionViewCutomProps<RowType>;
61
+ }
62
+ export interface NSALayoutCustomProps<RowType extends { id: string }, RowTypeInput = RowType> extends IBaseComponentProps, NSARouterMakerProps
63
+ {
64
+ action?: NSBarActionProps;
65
+ title?: NSBarTitleProps;
66
+ alerts?: NSBarAlertProps[];
67
+ pages?: NSALayoutPagesProps<RowType, RowTypeInput>;
68
+ children: JSX.Element | JSX.Element[];
69
+ onEnterPressed?: () => void;
70
+ onDidMounted?: () => void;
71
+ }
72
+ export interface NSALayoutProps<RowType extends { id: string }, RowTypeInput = RowType> extends NSALayoutCustomProps<RowType, RowTypeInput>
73
+ {
74
+ model?: {
75
+ id: string | null;
76
+ }
77
+ scope?: string;
78
+ logo?: string;
79
+ master_menu?: boolean;
80
+ background?: IBackgroundProps;
81
+ header?: {
82
+ enabled?: boolean,
83
+ right?: IHeaderRightProps;
84
+ left?: IHeaderLeftProps;
85
+ built_in?: {
86
+ logout?: boolean;
87
+ message?: boolean;
88
+ access?: boolean;
89
+ workspace?: boolean;
90
+ product?: boolean;
91
+ user?: boolean;
92
+ };
93
+ mega_menu?: boolean;
94
+ };
95
+ }
96
+
97
+ interface NSALayoutState
98
+ {
99
+ parmas: any | null;
100
+ product: ProductFullRow | null;
101
+ refresher: number;
102
+ message_count: number;
103
+ showDeleteDialog: boolean;
104
+ deleting: boolean;
105
+ deleteTotal: number;
106
+ deleteDone: number;
107
+ deleteFailed: number;
108
+ showMessageListDialog: boolean;
109
+ showAccessListDialog: boolean;
110
+ showProductListDialog: boolean;
111
+ showWorkspaceListDialog: boolean;
112
+ showUserDialog: boolean;
113
+ showMasterMenu: boolean;
114
+ ctf_enabled: boolean;
115
+ ctf_categories: CategoryRow[] | null;
116
+ ctf_fields: FieldRow[] | null;
117
+ }
118
+
119
+ export class NSALayout<RowType extends { id: string }, RowTypeInput = RowType> extends Component<NSALayoutProps<RowType, RowTypeInput>, NSALayoutState>
120
+ {
121
+ private onDidMounted_Called: boolean = false;
122
+ private onDidMounted_Ready: boolean = false;
123
+ private hasCFT: boolean;
124
+ private isLoggedIn: boolean;
125
+ private cache_product: CacheService<ProductFullRow>;
126
+ private cache_fields: CacheService<{
127
+ ctf_categories: CategoryRow[] | null;
128
+ ctf_fields: FieldRow[] | null;
129
+ }>;
130
+ private cache_message: CacheService<number>;
131
+ private cache_master_menu: CacheService<{ open: boolean }>;
132
+ NSASectionList_Ref = createRef<NSASectionList<RowType, RowTypeInput>>();
133
+ NSASectionEdit_Ref = createRef<NSASectionEdit<RowType, RowTypeInput>>();
134
+ NSASectionView_Ref = createRef<NSASectionView<RowType, RowTypeInput>>();
135
+ private cancelDelete: boolean = false;
136
+
137
+ constructor(props: NSALayoutProps<RowType, RowTypeInput>)
138
+ {
139
+ super(props);
140
+
141
+ // Account
142
+ this.isLoggedIn = this.props.account?.token_manager?.exists() ?? false;
143
+
144
+ // CFT
145
+ this.hasCFT = false;
146
+ if (this.props.pages?.entity)
147
+ try
148
+ {
149
+ let namirasoft = new NamirasoftMap(this.props.account.token_manager, props.notifier.onError);
150
+ let table = namirasoft.getByProductID(this.props.pages.entity.product_id).tables[this.props.pages.entity.name];
151
+ this.hasCFT = table.hasFieldTables();
152
+ } catch (error)
153
+ { }
154
+
155
+ // Cache Product
156
+ this.cache_product = ProductCacheService.get(this.props.product_id, console.error);
157
+
158
+ // Cache Fields
159
+ // todo this must be owner_id, otherwise switching between owners does not work correctly
160
+ let user_id = this.props.account.token_manager.getUserData(x => x.id, "");
161
+ let key_field = `ns-fields-${user_id}-${props.pages?.entity?.product_id ?? "*"}-${props.pages?.entity?.name ?? "*"}`;
162
+ this.cache_fields = new CacheService<{
163
+ ctf_categories: CategoryRow[] | null;
164
+ ctf_fields: FieldRow[] | null;
165
+ }>(key_field, new IStorageLocal(), 24 * 60, async () =>
166
+ {
167
+ let service = new CookieService(document.cookie, "ns-field-version");
168
+ return (this.props.account?.token_manager?.getUserData(x => x.id, "") ?? "") + "" + service.getString();
169
+ }, async () =>
170
+ {
171
+ let REACT_APP_BASE_URL_FIELD = new EnvService("REACT_APP_BASE_URL_FIELD", true).getString();
172
+ let server = new NamirasoftFieldServer(REACT_APP_BASE_URL_FIELD, props.account.token_manager, props.notifier.onError);
173
+ let ans: {
174
+ ctf_categories: CategoryRow[] | null;
175
+ ctf_fields: FieldRow[] | null;
176
+ } = { ctf_categories: null, ctf_fields: null };
177
+ if (this.props.pages)
178
+ {
179
+ let meta = new NamirasoftFieldMetaDatabase();
180
+ try
181
+ {
182
+ let filters: FilterItem[] = [];
183
+ filters.push(new FilterItem(meta.tables.category, meta.tables.category.columns.product_id, false, FilterItemOperator.all.equals, this.props.pages.entity.product_id));
184
+ filters.push(new FilterItem(meta.tables.category, meta.tables.category.columns.entity, false, FilterItemOperator.all.equals, this.props.pages.entity.name));
185
+ let res = await server.category.List(filters, null, null, []);
186
+ ans.ctf_categories = res.rows;
187
+ } catch (error)
188
+ {
189
+ ans.ctf_categories = [];
190
+ }
191
+ try
192
+ {
193
+ let filters: FilterItem[] = [];
194
+ filters.push(new FilterItem(meta.tables.field, meta.tables.field.columns.product_id, false, FilterItemOperator.all.equals, this.props.pages.entity.product_id));
195
+ filters.push(new FilterItem(meta.tables.field, meta.tables.field.columns.entity, false, FilterItemOperator.all.equals, this.props.pages.entity.name));
196
+ let res = await server.field.List(filters, null, null, []);
197
+ ans.ctf_fields = res.rows;
198
+ } catch (error)
199
+ {
200
+ ans.ctf_fields = [];
201
+ }
202
+ }
203
+ return ans;
204
+ });
205
+ // Cache Message
206
+ this.cache_message = new CacheService<number>("ns-messsage-count", new IStorageLocal(), 15, async () => "1.0.0", async () =>
207
+ {
208
+ let REACT_APP_BASE_URL_MESSAGE = new EnvService("REACT_APP_BASE_URL_MESSAGE", true).getString();
209
+ let server = new NamirasoftMessageServer(REACT_APP_BASE_URL_MESSAGE, this.props.account.token_manager, console.error);
210
+ let res = await server.message.GetNewCount();
211
+ return res.count;
212
+ });
213
+
214
+ this.cache_master_menu = NSACacheService.getMasterMenuStatus();
215
+ this.cache_message.onExpired = () => { this.reloadMessageCount(true); };
216
+
217
+ // State
218
+ this.state = {
219
+ parmas: null,
220
+ product: null,
221
+ refresher: 0,
222
+ message_count: 0,
223
+ showDeleteDialog: false,
224
+ deleting: false,
225
+ deleteTotal: 0,
226
+ deleteDone: 0,
227
+ deleteFailed: 0,
228
+ showMessageListDialog: false,
229
+ showAccessListDialog: false,
230
+ showProductListDialog: false,
231
+ showWorkspaceListDialog: false,
232
+ showUserDialog: false,
233
+ showMasterMenu: false,
234
+ ctf_enabled: this.hasCFT,
235
+ ctf_fields: null,
236
+ ctf_categories: null,
237
+ };
238
+ }
239
+ public reloadMessageCount(force: boolean)
240
+ {
241
+ if (force)
242
+ this.cache_message.del();
243
+ this.cache_message.get().then(count =>
244
+ {
245
+ this.setState(prev => ({ ...prev, message_count: count }));
246
+ }).catch(() => { });
247
+ }
248
+
249
+ private async runDelete(ids: string[], name: string): Promise<void>
250
+ {
251
+ const del = this.props.pages?.entity.server.delete;
252
+ if (!del)
253
+ return;
254
+
255
+ this.cancelDelete = false;
256
+ this.setState(prev => ({ ...prev, deleting: true, deleteTotal: ids.length, deleteDone: 0, deleteFailed: 0 }));
257
+
258
+ const deletedIds: string[] = [];
259
+ let failed = 0;
260
+ for (const id of ids)
261
+ {
262
+ if (this.cancelDelete)
263
+ break;
264
+ try
265
+ {
266
+ await del(id);
267
+ deletedIds.push(id);
268
+ this.setState(prev => ({ ...prev, deleteDone: prev.deleteDone + 1 }));
269
+ }
270
+ catch (error: any)
271
+ {
272
+ failed++;
273
+ this.setState(prev => ({ ...prev, deleteFailed: prev.deleteFailed + 1 }));
274
+ }
275
+ }
276
+
277
+ const cancelled = this.cancelDelete;
278
+ const remaining = ids.length - deletedIds.length - failed;
279
+
280
+ if (deletedIds.length > 0)
281
+ this.props.notifier.onSuccess(
282
+ deletedIds.length === 1
283
+ ? `${name} was deleted successfully.`
284
+ : `${deletedIds.length} ${name}s were deleted successfully.`
285
+ );
286
+ if (failed > 0)
287
+ this.props.notifier.onError(new Error(`Failed to delete ${failed} ${name}${failed > 1 ? "s" : ""}.`));
288
+ if (cancelled && remaining > 0)
289
+ this.props.notifier.onWarning(`Deletion cancelled. ${remaining} ${name}${remaining > 1 ? "s" : ""} not deleted.`);
290
+
291
+ if (deletedIds.length > 0)
292
+ {
293
+ if (this.props.pages?.list)
294
+ {
295
+ let table = this.NSASectionList_Ref.current?.NSATable_Main.current;
296
+ if (table)
297
+ {
298
+ let selected_ids = table.getSelectedIDs().filter(id => !deletedIds.includes(id));
299
+ let selected_rows = table.getSelectedRows().filter(row => !deletedIds.includes(row.id));
300
+ table.setSelectedIDs(selected_ids, selected_rows);
301
+ table.reload(null, null);
302
+ }
303
+ }
304
+ else if (this.props.pages?.edit)
305
+ {
306
+ if (this.props.pages.entity.client.getListURL)
307
+ this.props.url.redirect(this.props.pages.entity.client.getListURL(), {});
308
+ else if (this.props.pages.entity.client.getViewURL)
309
+ this.props.url.redirect(this.props.pages.entity.client.getViewURL(deletedIds[0]), {});
310
+ }
311
+ else if (this.props.pages?.view)
312
+ {
313
+ if (this.props.pages.entity.client.getListURL)
314
+ this.props.url.redirect(this.props.pages.entity.client.getListURL(), {});
315
+ }
316
+ }
317
+
318
+ this.setState(prev => ({ ...prev, deleting: false, showDeleteDialog: false }));
319
+ }
320
+
321
+ override componentDidMount(): void
322
+ {
323
+ this.onDidMounted_Called = false;
324
+ this.cache_product.get().then(product =>
325
+ {
326
+ this.setState(prev => ({ ...prev, product }));
327
+ })
328
+
329
+ this.cache_master_menu.get().then(status =>
330
+ {
331
+ this.setState(() => ({ showMasterMenu: status.open }));
332
+ })
333
+
334
+ if (this.hasCFT)
335
+ if (this.props.pages?.entity)
336
+ {
337
+ this.cache_fields.get().then(res =>
338
+ {
339
+ this.setState(prev => { return { ...prev, ctf_categories: res.ctf_categories ?? [], ctf_fields: res.ctf_fields ?? [] } });
340
+ });
341
+ }
342
+
343
+ if (this.isLoggedIn)
344
+ this.reloadMessageCount(false);
345
+
346
+ client.start();
347
+ KeyOperation.start();
348
+ LinkOperation.setNavigate(this.props.url.navigate);
349
+ }
350
+ override componentDidUpdate(): void
351
+ {
352
+ if (!this.onDidMounted_Called)
353
+ if (this.onDidMounted_Ready)
354
+ {
355
+ this.props.onDidMounted?.();
356
+ this.onDidMounted_Called = true;
357
+ }
358
+ }
359
+ override componentWillUnmount(): void
360
+ {
361
+ client.stop();
362
+ KeyOperation.stop();
363
+ }
364
+ override render()
365
+ {
366
+ this.onDidMounted_Ready = false;
367
+ let id = this.props.model?.id ?? null;
368
+ if (!id)
369
+ {
370
+ if (this.state.parmas === null)
371
+ return <UseParams onLoaded={parmas =>
372
+ {
373
+ this.setState({ parmas });
374
+ }} />;
375
+ id = this.state.parmas.id;
376
+ }
377
+
378
+ const hideDeleteDialog = async () => { this.setState(prev => ({ ...prev, showDeleteDialog: false })); };
379
+ const showDeleteDialog = async () => { this.setState(prev => ({ ...prev, showDeleteDialog: true })); };
380
+
381
+ let icons: IHeaderIconProps[] = [];
382
+
383
+ if (this.isLoggedIn)
384
+ {
385
+ if (this.props.header?.built_in?.message ?? true)
386
+ icons.push({
387
+ src: "https://static.namirasoft.com/image/namirasoft/message/logo/base.png", alt: "Message", onClicked: () =>
388
+ {
389
+ this.setState(prev => ({ ...prev, showMessageListDialog: true }));
390
+ this.reloadMessageCount(true);
391
+ },
392
+ children: <>
393
+ {
394
+ (
395
+ this.state.showMessageListDialog &&
396
+ <NSAMessageListDialog {...this.props}
397
+ onClose={() => { this.setState(prev => ({ ...prev, showMessageListDialog: false })); }}
398
+ onSeenAll={() =>
399
+ {
400
+ this.reloadMessageCount(true);
401
+ }}
402
+ onMessageClick={() =>
403
+ {
404
+ this.reloadMessageCount(true);
405
+ }}
406
+ />
407
+ )
408
+ }
409
+ </>,
410
+ count: this.state.message_count,
411
+ tooltip: "Message"
412
+ });
413
+ }
414
+ if (this.isLoggedIn)
415
+ if (this.props.header?.built_in?.access ?? true)
416
+ icons.push({
417
+ src: "https://static.namirasoft.com/image/namirasoft/access/logo/base.png", alt: "Access", onClicked: () =>
418
+ {
419
+ this.setState(prev => ({ ...prev, showAccessListDialog: true }));
420
+ },
421
+ children: <>
422
+ {
423
+ (this.state.showAccessListDialog) && <NSAAccessListDialog {...this.props} onClose={() =>
424
+ {
425
+ this.setState(prev => ({ ...prev, showAccessListDialog: false }));
426
+ }} />
427
+ }
428
+ </>,
429
+ tooltip: "Access"
430
+ });
431
+ if (this.isLoggedIn)
432
+ if (this.props.header?.built_in?.workspace ?? true)
433
+ icons.push({
434
+ src: "https://static.namirasoft.com/image/namirasoft/workspace/logo/base.png", alt: "Workspace", onClicked: () =>
435
+ {
436
+ this.setState(prev => ({ ...prev, showWorkspaceListDialog: true }));
437
+ },
438
+ children: <>
439
+ {
440
+ (this.state.showWorkspaceListDialog) && <NSAWorkspaceListDialog {...this.props} onClose={() =>
441
+ {
442
+ this.setState(prev => ({ ...prev, showWorkspaceListDialog: false }));
443
+ }} />
444
+ }
445
+ </>,
446
+ tooltip: "Workspace List"
447
+ });
448
+ if (this.props.header?.built_in?.product ?? true)
449
+ icons.push({
450
+ src: "https://static.namirasoft.com/image/concept/product-list/white.svg", alt: "Product", onClicked: () =>
451
+ {
452
+ this.setState(prev => ({ ...prev, showProductListDialog: true }));
453
+ },
454
+ children: <>
455
+ {
456
+ (this.state.showProductListDialog) && <NSAProductListDialog {...this.props} onClose={() =>
457
+ {
458
+ this.setState(prev => ({ ...prev, showProductListDialog: false }));
459
+ }} >
460
+ <></>
461
+ </NSAProductListDialog>
462
+ }
463
+ </>,
464
+ tooltip: "Product List"
465
+ });
466
+
467
+ if (this.isLoggedIn)
468
+ if (this.props.header?.built_in?.user ?? true)
469
+ icons.push({
470
+ src: "https://static.namirasoft.com/image/concept/user/white.svg",
471
+ alt: "User",
472
+ onClicked: () =>
473
+ {
474
+ this.setState(prev => ({ ...prev, showUserDialog: true }));
475
+ },
476
+ children: <>
477
+ {
478
+ (this.state.showUserDialog) &&
479
+ <NSAUserDialog
480
+ {...this.props}
481
+ client={client}
482
+ onClose={() => this.setState(prev => ({ ...prev, showUserDialog: false }))}
483
+ >
484
+ <></>
485
+ </NSAUserDialog>
486
+ }
487
+ </>,
488
+ tooltip: "User"
489
+ });
490
+
491
+ let name = "";
492
+ if (this.props.pages)
493
+ name = NamingConvention.lower_case_underscore.convert(this.props.pages.entity.name, NamingConvention.Pascal_Case_Space);
494
+
495
+ let getIDs: (() => string[]) | null = null;
496
+ let getRows: (() => RowType[]) | null = null;
497
+ if (this.props.pages?.list)
498
+ {
499
+ getIDs = () => { return this.NSASectionList_Ref.current?.getSelectedIDs() ?? []; };
500
+ getRows = () => { return this.NSASectionList_Ref.current?.getSelectedRows() ?? []; };
501
+ }
502
+ if (this.props.pages?.edit)
503
+ {
504
+ getIDs = () => [id].filter(id => id) as string[];
505
+ getRows = () => [this.NSASectionEdit_Ref.current?.getRow()].filter(row => row) as RowType[];
506
+ }
507
+ if (this.props.pages?.view)
508
+ {
509
+ getIDs = () => [id].filter(id => id) as string[];
510
+ getRows = () => [this.NSASectionView_Ref.current?.getRow()].filter(row => row) as RowType[];
511
+ }
512
+
513
+ let action: NSBarActionProps | undefined = { title: "", description: "", menus: {} };
514
+ if (this.props.action?.title)
515
+ action.title = this.props.action.title;
516
+ if (this.props.action?.description)
517
+ action.description = this.props.action.description;
518
+ if (this.props.action?.menus != null)
519
+ action.menus = { ...this.props.action?.menus };
520
+
521
+ if (getIDs && getRows)
522
+ if (this.props.pages?.entity.client.getMenus)
523
+ {
524
+ let menus = this.props.pages?.entity.client.getMenus(getIDs, getRows);
525
+ for (let key of Object.keys(menus))
526
+ {
527
+ if (!action.menus)
528
+ action.menus = {};
529
+ if (!action.menus[key])
530
+ action.menus[key] = [];
531
+ action.menus[key].push(...menus[key]);
532
+ }
533
+ }
534
+
535
+ let name_apply = "Apply";
536
+ let name_actions = "Actions";
537
+ if (!action.menus)
538
+ action.menus = {};
539
+ action.menus[name_apply] = [];
540
+ action.menus[name_actions] = [];
541
+
542
+ if (this.props.pages?.edit)
543
+ {
544
+ if (!action.title)
545
+ action.title = (this.props.pages.edit.isEdit ? "Edit" : "New") + " " + name;
546
+
547
+ // Menu
548
+ if (this.props.pages.edit.ui.action?.menus.apply ?? true)
549
+ action.menus[name_apply].push(Actions.apply({
550
+ action: (onFinished) =>
551
+ {
552
+ if (this.props.pages?.edit)
553
+ this.NSASectionEdit_Ref.current?.onApply(onFinished);
554
+ }
555
+ }));
556
+ }
557
+ else if (this.props.pages)
558
+ {
559
+ if (this.props.pages.entity.client.getNewURL)
560
+ if (this.props.pages.entity.server.create)
561
+ if (this.props.pages.ui?.action?.menus._new ?? true)
562
+ action.menus[name_apply].push(Actions._new(this.props, this.props.pages.entity));
563
+ }
564
+
565
+ if (getIDs)
566
+ if (this.props.pages)
567
+ {
568
+ if (this.props.pages.entity.client.getListURL)
569
+ if (this.props.pages.entity.server.list)
570
+ if (this.props.pages.ui?.action?.menus.list ?? true)
571
+ action.menus[name_actions].push(Actions.list(this.props, this.props.pages.entity));
572
+
573
+ if (this.props.pages.entity.client.getViewURL)
574
+ if (this.props.pages.entity.server.get)
575
+ if (this.props.pages.ui?.action?.menus.view ?? true)
576
+ action.menus[name_actions].push(Actions.view(this.props, this.props.pages.entity, getIDs));
577
+
578
+ if (this.props.pages.ui?.action?.menus.view_history ?? true)
579
+ action.menus[name_actions].push(Actions.viewHistory(getIDs));
580
+
581
+ if (this.props.pages.entity.client.getCopyURL)
582
+ if (this.props.pages.entity.server.create)
583
+ if (this.props.pages.ui?.action?.menus.copy ?? true)
584
+ action.menus[name_actions].push(Actions.copy(this.props, this.props.pages.entity, getIDs));
585
+
586
+ if (this.props.pages.entity.client.getEditURL)
587
+ if (this.props.pages.entity.server.update)
588
+ if (this.props.pages.ui?.action?.menus.edit ?? true)
589
+ action.menus[name_actions].push(Actions.edit(this.props, this.props.pages.entity, getIDs));
590
+
591
+ if (this.props.pages.entity.server.delete)
592
+ if (this.props.pages.ui?.action?.menus.delete ?? true)
593
+ action.menus[name_actions].push(Actions.delete(getIDs, {
594
+ action: showDeleteDialog,
595
+ showLoading: false
596
+ }));
597
+ }
598
+
599
+ let action_name = "";
600
+ let content;
601
+ if (this.props.pages?.list)
602
+ {
603
+ action_name = "List";
604
+ content = <NSASectionList<RowType, RowTypeInput>
605
+ ref={this.NSASectionList_Ref}
606
+ {...this.props}
607
+ {...this.props.pages.list}
608
+ entity={this.props.pages.entity}
609
+ onIDsChanged={() =>
610
+ {
611
+ this.setState(prev => ({ ...prev }));
612
+ this.props.pages?.list?.onIDsChanged?.();
613
+ }}
614
+ ctf={{
615
+ enabled: this.state.ctf_enabled,
616
+ categories: this.state.ctf_categories,
617
+ fields: this.state.ctf_fields
618
+ }}
619
+ deleteFieldCache={() => { this.cache_fields.del(); }}
620
+ >
621
+ {this.props.children}
622
+ </NSASectionList>
623
+ }
624
+ else if (this.props.pages?.edit)
625
+ {
626
+ if (this.props.pages.edit.isEdit)
627
+ action_name = "Edit";
628
+ else
629
+ action_name = "New";
630
+ content = <>
631
+ <NSASectionEdit
632
+ ref={this.NSASectionEdit_Ref}
633
+ {...this.props}
634
+ {...this.props.pages.edit}
635
+ entity={this.props.pages.entity}
636
+ ctf={{
637
+ enabled: this.state.ctf_enabled,
638
+ categories: this.state.ctf_categories,
639
+ fields: this.state.ctf_fields
640
+ }}
641
+ deleteFieldCache={() => { this.cache_fields.del(); }}
642
+ onRowLoaded={(row) =>
643
+ {
644
+ this.setState(prev => ({ ...prev }), () =>
645
+ {
646
+ this.props.pages?.edit?.onRowLoaded?.(row);
647
+ });
648
+ }}
649
+ model={{
650
+ id,
651
+ row: null
652
+ }}
653
+ >
654
+ {this.props.children}
655
+ </NSASectionEdit>
656
+ </>;
657
+ }
658
+ else if (this.props.pages?.view)
659
+ {
660
+ action_name = "View";
661
+ content = <NSASectionView
662
+ ref={this.NSASectionView_Ref}
663
+ {...this.props}
664
+ {...this.props.pages.view}
665
+ entity={this.props.pages.entity}
666
+ ctf={{
667
+ enabled: this.state.ctf_enabled,
668
+ categories: this.state.ctf_categories,
669
+ fields: this.state.ctf_fields
670
+ }}
671
+ deleteFieldCache={() => { this.cache_fields.del(); }}
672
+ onRowLoaded={(row) =>
673
+ {
674
+ this.setState(prev => ({ ...prev }), () =>
675
+ {
676
+ this.props.pages?.view?.onRowLoaded?.(row);
677
+ });
678
+ }}
679
+ model={{
680
+ id,
681
+ row: null
682
+ }}
683
+ >
684
+ {this.props.children}
685
+ </NSASectionView>
686
+ }
687
+ else
688
+ content = this.props.children;
689
+
690
+ let fullname = "";
691
+ if (this.props.pages?.entity.name)
692
+ {
693
+ let Name_Pascal_Space = NamingConvention.lower_case_underscore.convert(this.props.pages.entity.name, NamingConvention.Pascal_Case_Space);
694
+ if (action_name === "List")
695
+ fullname = Name_Pascal_Space + " " + action_name;
696
+ else
697
+ fullname = action_name + " " + Name_Pascal_Space;
698
+ }
699
+
700
+ if (!this.state.product)
701
+ return <NSLoading />;
702
+
703
+ let action_title = action?.title ?? "";
704
+ if (!action_title)
705
+ action_title = fullname;
706
+ if (!action_title)
707
+ action_title = this.state.product?.name
708
+
709
+ let action_description = action?.description ?? "";
710
+ if (!action_description)
711
+ action_description = this.state.product?.description ?? "";
712
+
713
+ let scope = this.props.scope ?? `${this.state.product.name} Console`;
714
+ let logo = this.props.logo ?? this.state.product.logo ?? "";
715
+ let background = this.props.background?.image ?? this.state.product.background ?? "";
716
+
717
+ this.onDidMounted_Ready = true;
718
+
719
+ let user_id = this.props.account.token_manager.getUserData(x => x.id, "");
720
+ let user_first_name = this.props.account.token_manager.getUserData(x => x.first_name, "");
721
+ let user_last_name = this.props.account.token_manager.getUserData(x => x.last_name, "");
722
+ let user_email = this.props.account.token_manager.getUserData(x => x.email, "");
723
+ let user_phone = this.props.account.token_manager.getUserData(x => x.phone ?? "", "");
724
+ return (
725
+ <div className="d-flex">
726
+ {
727
+ (this.props.master_menu ?? true) &&
728
+ <NSAMasterMenu
729
+ {...this.props}
730
+ open={this.state.showMasterMenu}
731
+ setOpen={(open) =>
732
+ {
733
+ this.cache_master_menu.set({ open });
734
+ this.setState(() => ({ showMasterMenu: open }));
735
+ }}
736
+ />
737
+ }
738
+ <NSLayout
739
+ {...this.props}
740
+ scope={scope}
741
+ logo={logo}
742
+ background={{ image: background, attachment: this.props.background?.attachment, size: this.props.background?.size }}
743
+ header={{
744
+ enabled: this.props.header?.enabled,
745
+ right: {
746
+ icons: [...icons, ...(this.props.header?.right?.icons ?? [])],
747
+ },
748
+ left: {
749
+ title: this.props.header?.left?.title
750
+ },
751
+ mega_menu: this.props.header?.mega_menu,
752
+ onCollapse: () =>
753
+ {
754
+ this.setState((prev) => ({ showMasterMenu: !prev.showMasterMenu }))
755
+ },
756
+ }}
757
+ action={{
758
+ title: action_title,
759
+ description: action_description,
760
+ menus: action.menus,
761
+ children: this.props.action?.children
762
+ }}
763
+ isLoggedIn={this.props.account?.token_manager?.exists}
764
+ notifications={this.props.notifier.getNotifications()}
765
+ >
766
+ <Helmet>
767
+ <meta name="ns-account-user-id" content={user_id} />
768
+ <meta name="ns-account-user-first-name" content={user_first_name} />
769
+ <meta name="ns-account-user-last-name" content={user_last_name} />
770
+ <meta name="ns-account-user-email" content={user_email} />
771
+ <meta name="ns-account-user-phone" content={user_phone} />
772
+ </Helmet>
773
+ {
774
+ this.state.showDeleteDialog && !this.state.deleting &&
775
+ <NSDialogDelete
776
+ title={"Delete Confirmation"}
777
+ description={
778
+ (getIDs && getIDs().length > 1)
779
+ ? `Are you sure you want to delete ${getIDs().length} ${name}s?`
780
+ : `Are you sure you want to delete ${name}?`
781
+ }
782
+ onClose={hideDeleteDialog}
783
+ onNo={{ action: hideDeleteDialog, showLoading: false }}
784
+ show_as_code={false}
785
+ onYes={{
786
+ showLoading: false,
787
+ action: async (onFinished) =>
788
+ {
789
+ try
790
+ {
791
+ let ids = getIDs ? getIDs() : [];
792
+ if (ids.length === 0)
793
+ throw new Error("Nothing was found to delete.");
794
+ await this.runDelete(ids, name);
795
+ }
796
+ catch (error: any)
797
+ {
798
+ this.props.notifier.onError(error);
799
+ this.setState(prev => ({ ...prev, showDeleteDialog: false }));
800
+ }
801
+ onFinished();
802
+ }
803
+ }}
804
+ >
805
+ <></>
806
+ </NSDialogDelete>}
807
+ {
808
+ this.state.showDeleteDialog && this.state.deleting &&
809
+ <NSDialogInfo
810
+ title={"Deleting"}
811
+ show_as_code={false}
812
+ onClose={() => { }}
813
+ closeButton={{ show: false }}
814
+ >
815
+ <NSProgressBar
816
+ progress={this.state.deleteTotal > 0
817
+ ? ((this.state.deleteDone + this.state.deleteFailed) / this.state.deleteTotal) * 100
818
+ : 0}
819
+ title={`Deleting... [${this.state.deleteDone + this.state.deleteFailed}/${this.state.deleteTotal}]`}
820
+ />
821
+ <NSSpace size={NSSpaceSizeType.MINI} />
822
+ <NSButtonRed
823
+ title="Cancel"
824
+ onClick={{
825
+ showLoading: false,
826
+ action: (onFinished) =>
827
+ {
828
+ this.cancelDelete = true;
829
+ onFinished();
830
+ }
831
+ }}
832
+ />
833
+ </NSDialogInfo>}
834
+ {
835
+ (!this.state.ctf_enabled || (this.state.ctf_fields && this.state.ctf_categories))
836
+ ?
837
+ content
838
+ :
839
+ <NSLoading />
840
+ }
841
+ </NSLayout>
842
+ </div >
843
+ );
844
+ }
842
845
  }