@jsb188/mday-web 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +20 -0
- package/src/utils/route-helpers.ts +602 -0
- package/tsconfig.json +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jsb188/mday-web",
|
|
3
|
+
"version": "1.1.8",
|
|
4
|
+
"description": "Web components and utilities for Marketday app.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "jsb188",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./utils/route": "./src/utils/route-helpers.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@jsb188/app": "1.1.8",
|
|
14
|
+
"@jsb188/mday": "1.1.8",
|
|
15
|
+
"@jsb188/react-web": "1.1.8"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import i18n from '@jsb188/app/i18n';
|
|
2
|
+
import { COMMON_ICON_NAMES } from '@jsb188/react-web/svgs/Icon';
|
|
3
|
+
import type { OrganizationFeatureEnum, OrganizationOperationEnum } from '../types/organization.d';
|
|
4
|
+
|
|
5
|
+
// Use this for report periods, etc
|
|
6
|
+
const CURRENT_YEAR = String(new Date().getFullYear());
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Constants; Re-usable rules
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const OP_FARMING = ['ARABLE', 'LIVESTOCK'];
|
|
13
|
+
|
|
14
|
+
const F = {
|
|
15
|
+
normal_logging: ['NORMAL_LOGGING'],
|
|
16
|
+
food_safety: ['FOOD_SAFETY', 'GLOBAL_GAP'],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Rules
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
type ValidRoutePath =
|
|
24
|
+
'/app'
|
|
25
|
+
| '/app/c/'
|
|
26
|
+
| '/app/ai-workflows'
|
|
27
|
+
| '/app/logs'
|
|
28
|
+
| '/app/seeding'
|
|
29
|
+
| '/app/transplanting'
|
|
30
|
+
| '/app/field-work'
|
|
31
|
+
| '/app/harvested'
|
|
32
|
+
| '/app/post-harvest'
|
|
33
|
+
| '/app/orders'
|
|
34
|
+
| '/app/globalgap/'
|
|
35
|
+
| '/app/cleaning'
|
|
36
|
+
| '/app/purchases'
|
|
37
|
+
| '/app/organic'
|
|
38
|
+
| '/app/hygiene'
|
|
39
|
+
| '/app/sanitation'
|
|
40
|
+
| '/app/materials'
|
|
41
|
+
| '/app/biosecurity'
|
|
42
|
+
| '/app/employees'
|
|
43
|
+
| '/app/vendors'
|
|
44
|
+
| '/app/markets'
|
|
45
|
+
| '/app/receipts'
|
|
46
|
+
| '/app/livestock'
|
|
47
|
+
| '/app/growers'
|
|
48
|
+
| '/app/foreign-growers';
|
|
49
|
+
|
|
50
|
+
interface RouteDictObj {
|
|
51
|
+
to: ValidRoutePath;
|
|
52
|
+
text: string;
|
|
53
|
+
iconName?: string;
|
|
54
|
+
allowedOperations?: OrganizationOperationEnum[];
|
|
55
|
+
notAllowedOperations?: OrganizationOperationEnum[];
|
|
56
|
+
requiredFeature?: OrganizationFeatureEnum[];
|
|
57
|
+
|
|
58
|
+
// These values prevent rendering flickers when calculating TOC/breadcrumbs between page renders
|
|
59
|
+
hasPhysicalToolbar?: 'ALWAYS' | 'NEVER' | ((parts: string[]) => boolean);
|
|
60
|
+
hasAsideNav?: 'ALWAYS' | 'NEVER' | ((parts: string[]) => boolean);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const ROUTES_DICT: Record<ValidRoutePath, RouteDictObj> = {
|
|
64
|
+
|
|
65
|
+
// Main /app/ routes
|
|
66
|
+
|
|
67
|
+
'/app': {
|
|
68
|
+
to: '/app',
|
|
69
|
+
text: 'app.home',
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
'/app/c/': {
|
|
73
|
+
to: '/app/c/',
|
|
74
|
+
text: 'app.route_ai_chat',
|
|
75
|
+
iconName: COMMON_ICON_NAMES.chat,
|
|
76
|
+
|
|
77
|
+
hasPhysicalToolbar: 'ALWAYS',
|
|
78
|
+
hasAsideNav: 'NEVER',
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Advanced
|
|
82
|
+
|
|
83
|
+
'/app/ai-workflows': {
|
|
84
|
+
to: '/app/ai-workflows',
|
|
85
|
+
text: 'form.ai_workflows',
|
|
86
|
+
iconName: COMMON_ICON_NAMES.ai_workflow,
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
'/app/logs': {
|
|
90
|
+
to: '/app/logs',
|
|
91
|
+
text: 'log.all_logs',
|
|
92
|
+
iconName: COMMON_ICON_NAMES.logs,
|
|
93
|
+
notAllowedOperations: ['GROWER_NETWORK'], // Temporary for now
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Arable
|
|
97
|
+
|
|
98
|
+
'/app/seeding': {
|
|
99
|
+
to: '/app/seeding',
|
|
100
|
+
text: 'log.seeding',
|
|
101
|
+
iconName: COMMON_ICON_NAMES.seeding,
|
|
102
|
+
|
|
103
|
+
allowedOperations: ['ARABLE'],
|
|
104
|
+
requiredFeature: F.normal_logging,
|
|
105
|
+
},
|
|
106
|
+
'/app/transplanting': {
|
|
107
|
+
to: '/app/transplanting',
|
|
108
|
+
text: 'log.transplanting',
|
|
109
|
+
iconName: COMMON_ICON_NAMES.transplanting,
|
|
110
|
+
|
|
111
|
+
allowedOperations: ['ARABLE'],
|
|
112
|
+
requiredFeature: F.normal_logging,
|
|
113
|
+
},
|
|
114
|
+
'/app/field-work': {
|
|
115
|
+
to: '/app/field-work',
|
|
116
|
+
text: 'log.field_work',
|
|
117
|
+
iconName: COMMON_ICON_NAMES.field_work,
|
|
118
|
+
|
|
119
|
+
allowedOperations: ['ARABLE'],
|
|
120
|
+
requiredFeature: F.normal_logging,
|
|
121
|
+
},
|
|
122
|
+
'/app/harvested': {
|
|
123
|
+
to: '/app/harvested',
|
|
124
|
+
text: 'log.harvested',
|
|
125
|
+
iconName: COMMON_ICON_NAMES.harvest,
|
|
126
|
+
|
|
127
|
+
allowedOperations: ['ARABLE'],
|
|
128
|
+
requiredFeature: F.normal_logging,
|
|
129
|
+
},
|
|
130
|
+
'/app/post-harvest': {
|
|
131
|
+
to: '/app/post-harvest',
|
|
132
|
+
text: 'log.post_harvest',
|
|
133
|
+
iconName: COMMON_ICON_NAMES.post_harvest,
|
|
134
|
+
|
|
135
|
+
allowedOperations: ['ARABLE'],
|
|
136
|
+
requiredFeature: F.normal_logging,
|
|
137
|
+
},
|
|
138
|
+
'/app/purchases': {
|
|
139
|
+
to: '/app/purchases',
|
|
140
|
+
text: 'log.purchases',
|
|
141
|
+
iconName: COMMON_ICON_NAMES.invoice,
|
|
142
|
+
|
|
143
|
+
allowedOperations: OP_FARMING,
|
|
144
|
+
requiredFeature: F.normal_logging,
|
|
145
|
+
},
|
|
146
|
+
'/app/orders': {
|
|
147
|
+
to: '/app/orders',
|
|
148
|
+
text: 'form.sales_orders',
|
|
149
|
+
iconName: COMMON_ICON_NAMES.receipt,
|
|
150
|
+
|
|
151
|
+
allowedOperations: OP_FARMING,
|
|
152
|
+
requiredFeature: F.normal_logging,
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Arable; Reports
|
|
156
|
+
|
|
157
|
+
'/app/globalgap/': {
|
|
158
|
+
to: ('/app/globalgap/' + CURRENT_YEAR) as ValidRoutePath,
|
|
159
|
+
text: 'product.report.GLOBAL_GAP',
|
|
160
|
+
iconName: COMMON_ICON_NAMES.generic_report,
|
|
161
|
+
|
|
162
|
+
allowedOperations: OP_FARMING,
|
|
163
|
+
requiredFeature: ['GLOBAL_GAP'],
|
|
164
|
+
|
|
165
|
+
hasPhysicalToolbar: (parts: string[]) => parts.length > 4,
|
|
166
|
+
hasAsideNav: (parts: string[]) => parts.length > 4,
|
|
167
|
+
},
|
|
168
|
+
'/app/cleaning': {
|
|
169
|
+
to: '/app/cleaning',
|
|
170
|
+
text: 'product.report.CLEANING',
|
|
171
|
+
iconName: COMMON_ICON_NAMES.SANITATION,
|
|
172
|
+
|
|
173
|
+
allowedOperations: OP_FARMING,
|
|
174
|
+
requiredFeature: F.food_safety,
|
|
175
|
+
},
|
|
176
|
+
'/app/organic': {
|
|
177
|
+
to: '/app/organic',
|
|
178
|
+
text: 'product.report.ORGANIC_CERTIFICATION',
|
|
179
|
+
iconName: COMMON_ICON_NAMES.generic_report,
|
|
180
|
+
|
|
181
|
+
allowedOperations: OP_FARMING,
|
|
182
|
+
requiredFeature: ['ORGANIC_CERTIFICATION'],
|
|
183
|
+
|
|
184
|
+
// This will need to change later if we're introducing "period" table of contents page selection
|
|
185
|
+
hasPhysicalToolbar: 'ALWAYS',
|
|
186
|
+
hasAsideNav: 'ALWAYS',
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// Arable; Food Safety
|
|
190
|
+
|
|
191
|
+
'/app/hygiene': {
|
|
192
|
+
to: '/app/hygiene',
|
|
193
|
+
text: 'log.hygiene',
|
|
194
|
+
iconName: COMMON_ICON_NAMES.HYGIENE,
|
|
195
|
+
|
|
196
|
+
allowedOperations: OP_FARMING,
|
|
197
|
+
requiredFeature: F.food_safety,
|
|
198
|
+
},
|
|
199
|
+
'/app/sanitation': {
|
|
200
|
+
to: '/app/sanitation',
|
|
201
|
+
text: 'log.sanitation',
|
|
202
|
+
iconName: COMMON_ICON_NAMES.SANITATION,
|
|
203
|
+
|
|
204
|
+
allowedOperations: OP_FARMING,
|
|
205
|
+
requiredFeature: F.food_safety,
|
|
206
|
+
},
|
|
207
|
+
'/app/materials': {
|
|
208
|
+
to: '/app/materials',
|
|
209
|
+
text: 'log.materials',
|
|
210
|
+
iconName: COMMON_ICON_NAMES.MATERIALS,
|
|
211
|
+
|
|
212
|
+
allowedOperations: OP_FARMING,
|
|
213
|
+
requiredFeature: F.food_safety,
|
|
214
|
+
},
|
|
215
|
+
'/app/biosecurity': {
|
|
216
|
+
to: '/app/biosecurity',
|
|
217
|
+
text: 'log.biosecurity',
|
|
218
|
+
iconName: COMMON_ICON_NAMES.BIOSECURITY,
|
|
219
|
+
|
|
220
|
+
allowedOperations: OP_FARMING,
|
|
221
|
+
requiredFeature: F.food_safety,
|
|
222
|
+
},
|
|
223
|
+
'/app/employees': {
|
|
224
|
+
to: '/app/employees',
|
|
225
|
+
text: 'log.employees',
|
|
226
|
+
iconName: COMMON_ICON_NAMES.EMPLOYEES,
|
|
227
|
+
|
|
228
|
+
allowedOperations: OP_FARMING,
|
|
229
|
+
requiredFeature: F.food_safety,
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// Farmers Market
|
|
233
|
+
'/app/vendors': {
|
|
234
|
+
to: '/app/vendors',
|
|
235
|
+
text: 'form.vendors',
|
|
236
|
+
iconName: COMMON_ICON_NAMES.shop_vendor,
|
|
237
|
+
|
|
238
|
+
allowedOperations: ['FARMERS_MARKET'],
|
|
239
|
+
requiredFeature: F.normal_logging
|
|
240
|
+
},
|
|
241
|
+
'/app/markets': {
|
|
242
|
+
to: '/app/markets',
|
|
243
|
+
text: 'form.markets',
|
|
244
|
+
iconName: COMMON_ICON_NAMES.shop_market,
|
|
245
|
+
|
|
246
|
+
allowedOperations: ['FARMERS_MARKET'],
|
|
247
|
+
requiredFeature: ['CAL_EVENTS'],
|
|
248
|
+
},
|
|
249
|
+
'/app/receipts': {
|
|
250
|
+
to: ('/app/receipts?s=1') as ValidRoutePath,
|
|
251
|
+
text: 'form.market_receipts',
|
|
252
|
+
iconName: COMMON_ICON_NAMES.market_receipt,
|
|
253
|
+
|
|
254
|
+
allowedOperations: ['FARMERS_MARKET'],
|
|
255
|
+
requiredFeature: F.normal_logging
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
// Livestock
|
|
259
|
+
'/app/livestock': {
|
|
260
|
+
to: '/app/livestock',
|
|
261
|
+
text: 'form.livestock',
|
|
262
|
+
iconName: COMMON_ICON_NAMES.livestock,
|
|
263
|
+
|
|
264
|
+
allowedOperations: ['LIVESTOCK'],
|
|
265
|
+
requiredFeature: F.normal_logging
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
// Grower Network
|
|
269
|
+
'/app/growers': {
|
|
270
|
+
to: '/app/growers',
|
|
271
|
+
text: 'form.domestic_growers',
|
|
272
|
+
iconName: COMMON_ICON_NAMES.growers,
|
|
273
|
+
|
|
274
|
+
allowedOperations: ['GROWER_NETWORK'],
|
|
275
|
+
},
|
|
276
|
+
'/app/foreign-growers': {
|
|
277
|
+
to: '/app/foreign-growers',
|
|
278
|
+
text: 'form.foreign_growers',
|
|
279
|
+
iconName: COMMON_ICON_NAMES.foreign_growers,
|
|
280
|
+
|
|
281
|
+
allowedOperations: ['GROWER_NETWORK'],
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Make pathname to a route in Marketday app.
|
|
287
|
+
* @param routeName - The name of the route.
|
|
288
|
+
* @param pathSegment - Optional additional path segment to append.
|
|
289
|
+
* @returns The full pathname for the route.
|
|
290
|
+
*/
|
|
291
|
+
|
|
292
|
+
export function makePathname(routePath: ValidRoutePath, pathSegment?: string | null): ValidRoutePath {
|
|
293
|
+
const pathExists = !!ROUTES_DICT[routePath];
|
|
294
|
+
if (pathExists && routePath.endsWith('/')) {
|
|
295
|
+
return (pathSegment ? `${routePath}${pathSegment}` : routePath.substring(0, routePath.length - 1)) as ValidRoutePath;
|
|
296
|
+
}
|
|
297
|
+
return pathExists ? routePath : '/app';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check if route is valid.
|
|
302
|
+
* @param routeName - The name of the route.
|
|
303
|
+
* @param pathSegment - Optional additional path segment to append.
|
|
304
|
+
* @returns The full pathname for the route.
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
export function isRouteValid(routePath: ValidRoutePath, pathSegment?: string | null): boolean {
|
|
308
|
+
const routeDict = !!ROUTES_DICT[routePath];
|
|
309
|
+
return (
|
|
310
|
+
!!routeDict &&
|
|
311
|
+
(!routePath.endsWith('/') || !!pathSegment)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get route config from pathname.
|
|
317
|
+
* NOTE: This is the best way to prevent page flickers due to breadcrumbs/TOC calculations/resets/etc.
|
|
318
|
+
* @param pathname - The pathname to check.
|
|
319
|
+
* @returns The route name if found.
|
|
320
|
+
*/
|
|
321
|
+
|
|
322
|
+
const ROUTES_DICT_ORDERED = Object.keys(ROUTES_DICT).sort((a, b) => b.length - a.length);
|
|
323
|
+
|
|
324
|
+
interface RouteConfigObj extends Omit<RouteDictObj, 'hasPhysicalToolbar' | 'hasAsideNav'> {
|
|
325
|
+
routeName: ValidRoutePath;
|
|
326
|
+
scrollResetKey: string;
|
|
327
|
+
allowed: boolean;
|
|
328
|
+
hasPhysicalToolbar: boolean;
|
|
329
|
+
hasAsideNav: boolean;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function getRouteConfigs(
|
|
333
|
+
pathname: ValidRoutePath | string,
|
|
334
|
+
operation?: OrganizationOperationEnum | null,
|
|
335
|
+
orgFeatures?: OrganizationFeatureEnum[] | null,
|
|
336
|
+
): RouteConfigObj {
|
|
337
|
+
|
|
338
|
+
for (const routeName of ROUTES_DICT_ORDERED) {
|
|
339
|
+
if (pathname.startsWith(routeName)) {
|
|
340
|
+
|
|
341
|
+
// Scroll position fix + breadcrumb / TOC cleanup on unmount
|
|
342
|
+
// This will retain scroll position for deeper links,
|
|
343
|
+
// ie. "/app/globalgap/2023/.." will be retained
|
|
344
|
+
|
|
345
|
+
const routeDict = ROUTES_DICT[routeName as ValidRoutePath];
|
|
346
|
+
const pathParts = pathname.split('/');
|
|
347
|
+
|
|
348
|
+
let scrollResetKey: string;
|
|
349
|
+
if (pathParts.length >= 3) {
|
|
350
|
+
// scroll reset key here is full path minus last part
|
|
351
|
+
scrollResetKey = pathParts.slice(0, pathParts.length - 1).join('/');
|
|
352
|
+
} else {
|
|
353
|
+
scrollResetKey = pathParts.slice(0, 3).join('/');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const hasAsideNav = !!routeDict.hasAsideNav && routeDict.hasAsideNav !== 'NEVER' && (
|
|
357
|
+
routeDict.hasAsideNav === 'ALWAYS' ||
|
|
358
|
+
(typeof routeDict.hasAsideNav === 'function' && routeDict.hasAsideNav(pathParts))
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const hasPhysicalToolbar = !!routeDict.hasPhysicalToolbar && routeDict.hasPhysicalToolbar !== 'NEVER' && (
|
|
362
|
+
routeDict.hasPhysicalToolbar === 'ALWAYS' ||
|
|
363
|
+
(typeof routeDict.hasPhysicalToolbar === 'function' && routeDict.hasPhysicalToolbar(pathParts))
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
...routeDict,
|
|
368
|
+
text: i18n.has(routeDict.text) ? i18n.t(routeDict.text) : routeDict.text,
|
|
369
|
+
routeName: routeName as ValidRoutePath,
|
|
370
|
+
scrollResetKey,
|
|
371
|
+
allowed: isRouteAllowed(pathname, operation, orgFeatures),
|
|
372
|
+
hasPhysicalToolbar,
|
|
373
|
+
hasAsideNav,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
routeName: '/__unknown',
|
|
380
|
+
scrollResetKey: '',
|
|
381
|
+
allowed: false,
|
|
382
|
+
hasPhysicalToolbar: false,
|
|
383
|
+
hasAsideNav: false,
|
|
384
|
+
} as any;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Check if this organization's operation allows access to this route path
|
|
389
|
+
* @param pathname - pathname to check.
|
|
390
|
+
* @param operation - Organization operation.
|
|
391
|
+
* @param orgFeatures - Enabled features for organization.
|
|
392
|
+
*/
|
|
393
|
+
|
|
394
|
+
export function isRouteAllowed(
|
|
395
|
+
pathname: ValidRoutePath | string,
|
|
396
|
+
operation?: OrganizationOperationEnum | null,
|
|
397
|
+
orgFeatures?: OrganizationFeatureEnum[] | null,
|
|
398
|
+
): boolean {
|
|
399
|
+
|
|
400
|
+
let routeDict = ROUTES_DICT[pathname as ValidRoutePath];
|
|
401
|
+
if (!routeDict) {
|
|
402
|
+
const parts = pathname.split('/');
|
|
403
|
+
for (let i = parts.length; i > 0; i--) {
|
|
404
|
+
const subPath = parts.slice(0, i).join('/');
|
|
405
|
+
|
|
406
|
+
// @ts-ignore - sub paths may not be valid route paths
|
|
407
|
+
routeDict = ROUTES_DICT[subPath] || ROUTES_DICT[subPath + '/'];
|
|
408
|
+
if (routeDict || i <= 3) {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!routeDict) {
|
|
415
|
+
// Assume true if there are no rules set
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
(!routeDict.allowedOperations || routeDict.allowedOperations.includes(operation || '')) &&
|
|
421
|
+
(!routeDict.notAllowedOperations || !routeDict.notAllowedOperations.includes(operation || '')) &&
|
|
422
|
+
// If {orgFeature} is null, assume data is not finished loading yet, and allow "benefit of doubt" access
|
|
423
|
+
(!orgFeatures || !routeDict.requiredFeature || routeDict.requiredFeature.some((feature) => orgFeatures.includes(feature)))
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Helper; because i18n isn't registered when this file opens, we gotta do this
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
function routesDictI18n(routeDictObj: RouteDictObj) {
|
|
432
|
+
return {
|
|
433
|
+
...routeDictObj,
|
|
434
|
+
text: i18n.t(routeDictObj.text),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Helper; get static nav list based on organization's operation & features
|
|
440
|
+
*/
|
|
441
|
+
|
|
442
|
+
interface NavListItem {
|
|
443
|
+
break: never;
|
|
444
|
+
to: string;
|
|
445
|
+
text: string;
|
|
446
|
+
iconName: string;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
interface NavBreakItem {
|
|
450
|
+
break: boolean;
|
|
451
|
+
to: never;
|
|
452
|
+
text: never;
|
|
453
|
+
iconName: never;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
export type NavigationItem = NavListItem | NavBreakItem;
|
|
457
|
+
|
|
458
|
+
export function getNavigationList(
|
|
459
|
+
operation: OrganizationOperationEnum | null,
|
|
460
|
+
orgFeatures?: OrganizationFeatureEnum[],
|
|
461
|
+
): NavigationItem[] {
|
|
462
|
+
|
|
463
|
+
const breakItem = {
|
|
464
|
+
break: true,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
let navListArr: {
|
|
468
|
+
text: string;
|
|
469
|
+
initialExpanded?: boolean;
|
|
470
|
+
navList: (RouteDictObj | { break: boolean })[];
|
|
471
|
+
}[] = [];
|
|
472
|
+
|
|
473
|
+
switch (operation) {
|
|
474
|
+
case 'ARABLE':
|
|
475
|
+
navListArr = [
|
|
476
|
+
{
|
|
477
|
+
text: i18n.t('form.reports'),
|
|
478
|
+
navList: [
|
|
479
|
+
ROUTES_DICT['/app/organic'],
|
|
480
|
+
ROUTES_DICT['/app/globalgap/'],
|
|
481
|
+
]
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
text: i18n.t('log.food_safety'),
|
|
485
|
+
navList: [
|
|
486
|
+
ROUTES_DICT['/app/cleaning'],
|
|
487
|
+
// ROUTES_DICT['/app/hygiene'],
|
|
488
|
+
// ROUTES_DICT['/app/sanitation'],
|
|
489
|
+
// ROUTES_DICT['/app/materials'],
|
|
490
|
+
// ROUTES_DICT['/app/biosecurity'],
|
|
491
|
+
// ROUTES_DICT['/app/employees'],
|
|
492
|
+
]
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
text: i18n.t(`org.type_active.${operation}`),
|
|
496
|
+
navList: [
|
|
497
|
+
ROUTES_DICT['/app/seeding'],
|
|
498
|
+
ROUTES_DICT['/app/transplanting'],
|
|
499
|
+
ROUTES_DICT['/app/field-work'],
|
|
500
|
+
ROUTES_DICT['/app/harvested'],
|
|
501
|
+
ROUTES_DICT['/app/post-harvest'],
|
|
502
|
+
|
|
503
|
+
breakItem,
|
|
504
|
+
|
|
505
|
+
ROUTES_DICT['/app/purchases'],
|
|
506
|
+
ROUTES_DICT['/app/orders'],
|
|
507
|
+
]
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
break;
|
|
511
|
+
case 'LIVESTOCK':
|
|
512
|
+
navListArr = [
|
|
513
|
+
{
|
|
514
|
+
text: i18n.t(`org.type_active.${operation}`),
|
|
515
|
+
navList: [
|
|
516
|
+
ROUTES_DICT['/app/livestock'],
|
|
517
|
+
{ ...ROUTES_DICT['/app/purchases'], text: i18n.t('log.supply_purchases') },
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
];
|
|
521
|
+
break;
|
|
522
|
+
case 'FARMERS_MARKET':
|
|
523
|
+
navListArr = [
|
|
524
|
+
{
|
|
525
|
+
text: i18n.t(`org.type_active.${operation}`),
|
|
526
|
+
navList: [
|
|
527
|
+
ROUTES_DICT['/app/vendors'],
|
|
528
|
+
ROUTES_DICT['/app/markets'],
|
|
529
|
+
ROUTES_DICT['/app/receipts'],
|
|
530
|
+
]
|
|
531
|
+
},
|
|
532
|
+
];
|
|
533
|
+
break;
|
|
534
|
+
case 'GROWER_NETWORK':
|
|
535
|
+
navListArr = [
|
|
536
|
+
{
|
|
537
|
+
text: i18n.t(`org.type_active.${operation}`),
|
|
538
|
+
navList: [
|
|
539
|
+
ROUTES_DICT['/app/growers'],
|
|
540
|
+
ROUTES_DICT['/app/foreign-growers'],
|
|
541
|
+
]
|
|
542
|
+
}
|
|
543
|
+
];
|
|
544
|
+
break;
|
|
545
|
+
default:
|
|
546
|
+
navListArr = [];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// @ts-ignore
|
|
550
|
+
navListArr = [{
|
|
551
|
+
...ROUTES_DICT['/app'],
|
|
552
|
+
iconName: COMMON_ICON_NAMES[operation!] || 'home',
|
|
553
|
+
},
|
|
554
|
+
breakItem,
|
|
555
|
+
// @ts-ignore
|
|
556
|
+
].concat(navListArr).concat([{
|
|
557
|
+
text: i18n.t('form.advanced'),
|
|
558
|
+
initialExpanded: false,
|
|
559
|
+
navList: [
|
|
560
|
+
ROUTES_DICT['/app/ai-workflows'],
|
|
561
|
+
ROUTES_DICT['/app/logs']
|
|
562
|
+
]
|
|
563
|
+
}] as any);
|
|
564
|
+
|
|
565
|
+
const mapNavItems = (item: any) => {
|
|
566
|
+
if (Array.isArray(item)) {
|
|
567
|
+
const [navItem, ...requiredFeatures] = item;
|
|
568
|
+
if (requiredFeatures.some(feature => orgFeatures?.includes?.(feature))) {
|
|
569
|
+
return navItem;
|
|
570
|
+
}
|
|
571
|
+
return null;
|
|
572
|
+
} else if (item?.navList) {
|
|
573
|
+
item.navList = item.navList.reduce((acc: any[], x: any, i: number) => {
|
|
574
|
+
const mappedItem = mapNavItems(x);
|
|
575
|
+
if (
|
|
576
|
+
mappedItem &&
|
|
577
|
+
(mappedItem.to || (acc.length && i !== (item.navList.length - 1)))
|
|
578
|
+
) {
|
|
579
|
+
if (mappedItem.text && i18n.has(mappedItem.text)) {
|
|
580
|
+
mappedItem.text = i18n.t(mappedItem.text);
|
|
581
|
+
}
|
|
582
|
+
acc.push(mappedItem);
|
|
583
|
+
}
|
|
584
|
+
return acc;
|
|
585
|
+
}, []);
|
|
586
|
+
|
|
587
|
+
if (!item.navList.length) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
} else if (item?.to && !isRouteAllowed(item.to, operation, orgFeatures)) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (item?.text && i18n.has(item.text)) {
|
|
595
|
+
item.text = i18n.t(item.text);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return item;
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
return navListArr.map(mapNavItems).filter(Boolean);
|
|
602
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"allowImportingTsExtensions": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"esModuleInterop": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|