namirasoft-account-react 1.4.433 → 1.4.435

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