namirasoft-account-react 1.4.430 → 1.4.431

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 (124) 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/UseParams.d.ts +1 -1
  7. package/dist/components/NSAAccessListDialog.d.ts +2 -2
  8. package/dist/components/NSAAccessListDialog.module.css +71 -71
  9. package/dist/components/NSAMasterMenu.d.ts +1 -1
  10. package/dist/components/NSAMasterMenu.module.css +248 -248
  11. package/dist/components/NSAMasterMenuItem.d.ts +1 -1
  12. package/dist/components/NSAMessageListDialog.d.ts +1 -1
  13. package/dist/components/NSAMessageListDialog.module.css +99 -99
  14. package/dist/components/NSAProductListDialog.d.ts +1 -1
  15. package/dist/components/NSAProductListDialog.module.css +44 -44
  16. package/dist/components/NSAReorderDialog.d.ts +1 -1
  17. package/dist/components/NSAReorderDialog.module.css +49 -49
  18. package/dist/components/NSASortDialog.d.ts +1 -1
  19. package/dist/components/NSAUserDialog.d.ts +1 -1
  20. package/dist/components/NSAUserDialog.module.css +67 -67
  21. package/dist/components/NSAWorkspaceListDialog.d.ts +1 -1
  22. package/dist/components/NSAWorkspaceListDialog.module.css +41 -41
  23. package/dist/components/NSBoxSecret.d.ts +1 -1
  24. package/dist/components/NSBoxSecret.module.css +23 -23
  25. package/dist/components/NSLabelSecret.d.ts +1 -1
  26. package/dist/components/quickfilter/NSAFilterBoxEnum.module.css +45 -0
  27. package/dist/components/quickfilter/NSAQuickFilterBar.d.ts +1 -1
  28. package/dist/components/quickfilter/NSAQuickFilterBar.module.css +20 -0
  29. package/dist/components/quickfilter/NSAQuickFilterDialog.d.ts +1 -1
  30. package/dist/components/quickfilter/NSAQuickFilterDialog.module.css +75 -0
  31. package/dist/index.css +0 -0
  32. package/dist/layouts/NSALayout.d.ts +1 -1
  33. package/dist/layouts/NSASectionEdit.d.ts +1 -1
  34. package/dist/layouts/NSASectionEditTabPage.d.ts +1 -1
  35. package/dist/layouts/NSASectionEditTabPage.module.css +4 -4
  36. package/dist/layouts/NSASectionList.d.ts +1 -1
  37. package/dist/layouts/NSASectionList.module.css +31 -31
  38. package/dist/layouts/NSASectionView.d.ts +1 -1
  39. package/dist/layouts/NSASectionViewTabMore.d.ts +1 -1
  40. package/dist/layouts/NSASectionViewTabPage.d.ts +1 -1
  41. package/dist/layouts/NSASectionViewTabPage.module.css +47 -47
  42. package/dist/pages/NSAConsentPage.module.css +26 -26
  43. package/dist/pages/NSAEmailVerificationPage.d.ts +1 -1
  44. package/dist/pages/NSAHomePage.d.ts +1 -1
  45. package/dist/pages/NSAHomePage.module.css +41 -41
  46. package/dist/pages/NSALoginPage.d.ts +1 -1
  47. package/dist/pages/NSALoginPage.module.css +26 -26
  48. package/dist/pages/NSAPhoneVerificationPage.d.ts +1 -1
  49. package/dist/pages/NSAVerificationPage.d.ts +1 -1
  50. package/dist/pages/NSAVerificationPage.module.css +31 -31
  51. package/package.json +94 -94
  52. package/public/index.html +21 -21
  53. package/src/App.css +31 -31
  54. package/src/App.tsx +19 -19
  55. package/src/CTFRow.ts +7 -7
  56. package/src/IEntityInfo.ts +33 -33
  57. package/src/NSACacheService.ts +120 -120
  58. package/src/NSAFilterOperators.ts +92 -92
  59. package/src/NSARouterMaker.tsx +139 -139
  60. package/src/NSARouterMakerConfig.ts +15 -15
  61. package/src/NSARouterMakerProps.ts +22 -22
  62. package/src/Router.tsx +45 -45
  63. package/src/UseParams.tsx +18 -18
  64. package/src/components/NSAAccessListDialog.module.css +71 -71
  65. package/src/components/NSAAccessListDialog.tsx +178 -178
  66. package/src/components/NSAMasterMenu.module.css +248 -248
  67. package/src/components/NSAMasterMenu.tsx +169 -169
  68. package/src/components/NSAMasterMenuItem.tsx +250 -250
  69. package/src/components/NSAMessageListDialog.module.css +99 -99
  70. package/src/components/NSAMessageListDialog.tsx +141 -141
  71. package/src/components/NSAProductListDialog.module.css +44 -44
  72. package/src/components/NSAProductListDialog.tsx +88 -88
  73. package/src/components/NSAReorderDialog.module.css +49 -49
  74. package/src/components/NSAReorderDialog.tsx +149 -149
  75. package/src/components/NSASortDialog.tsx +117 -117
  76. package/src/components/NSATable.tsx +489 -489
  77. package/src/components/NSAUserDialog.module.css +67 -67
  78. package/src/components/NSAUserDialog.tsx +122 -122
  79. package/src/components/NSAWorkspaceListDialog.module.css +41 -41
  80. package/src/components/NSAWorkspaceListDialog.tsx +147 -147
  81. package/src/components/NSBoxSecret.module.css +23 -23
  82. package/src/components/NSBoxSecret.tsx +258 -258
  83. package/src/components/NSLabelSecret.tsx +37 -37
  84. package/src/components/quickfilter/NSAFilterBoxBase.tsx +55 -55
  85. package/src/components/quickfilter/NSAFilterBoxBoolean.tsx +54 -54
  86. package/src/components/quickfilter/NSAFilterBoxDate.tsx +104 -104
  87. package/src/components/quickfilter/NSAFilterBoxDateTime.tsx +105 -105
  88. package/src/components/quickfilter/NSAFilterBoxEnum.module.css +44 -44
  89. package/src/components/quickfilter/NSAFilterBoxEnum.tsx +79 -79
  90. package/src/components/quickfilter/NSAFilterBoxNumber.tsx +136 -136
  91. package/src/components/quickfilter/NSAFilterBoxString.tsx +81 -81
  92. package/src/components/quickfilter/NSAFilterBoxTime.tsx +101 -101
  93. package/src/components/quickfilter/NSAQuickFilterBar.module.css +19 -19
  94. package/src/components/quickfilter/NSAQuickFilterBar.tsx +114 -114
  95. package/src/components/quickfilter/NSAQuickFilterDialog.module.css +74 -74
  96. package/src/components/quickfilter/NSAQuickFilterDialog.tsx +260 -260
  97. package/src/css.d.ts +13 -13
  98. package/src/formatters/SecretFormatter.tsx +24 -24
  99. package/src/index.tsx +24 -24
  100. package/src/layouts/Actions.ts +145 -145
  101. package/src/layouts/CFTUtil.ts +18 -18
  102. package/src/layouts/NSALayout.tsx +841 -841
  103. package/src/layouts/NSASectionEdit.tsx +260 -260
  104. package/src/layouts/NSASectionEditTabPage.module.css +4 -4
  105. package/src/layouts/NSASectionEditTabPage.tsx +250 -250
  106. package/src/layouts/NSASectionList.module.css +31 -31
  107. package/src/layouts/NSASectionList.tsx +149 -149
  108. package/src/layouts/NSASectionView.tsx +100 -100
  109. package/src/layouts/NSASectionViewTabMore.tsx +77 -77
  110. package/src/layouts/NSASectionViewTabPage.module.css +47 -47
  111. package/src/layouts/NSASectionViewTabPage.tsx +419 -419
  112. package/src/main.ts +46 -46
  113. package/src/pages/NSAConsentPage.module.css +26 -26
  114. package/src/pages/NSAConsentPage.tsx +120 -120
  115. package/src/pages/NSAEmailVerificationPage.tsx +30 -30
  116. package/src/pages/NSAHomePage.module.css +41 -41
  117. package/src/pages/NSAHomePage.tsx +102 -102
  118. package/src/pages/NSALoginPage.module.css +26 -26
  119. package/src/pages/NSALoginPage.tsx +87 -87
  120. package/src/pages/NSAPhoneVerificationPage.tsx +94 -94
  121. package/src/pages/NSAVerificationPage.module.css +31 -31
  122. package/src/pages/NSAVerificationPage.tsx +186 -186
  123. package/src/pages/PaymentRequired.tsx +74 -74
  124. package/tsconfig.json +43 -43
@@ -1,842 +1,842 @@
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, 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
+ }
842
842
  }