|
1 | 1 | import { z } from 'zod'; |
2 | 2 |
|
3 | 3 | /** |
4 | | - * Navigation Tab Type |
| 4 | + * Base Navigation Item Schema |
| 5 | + * Shared properties for all navigation types. |
5 | 6 | */ |
6 | | -export const TabType = z.enum([ |
7 | | - 'entity', // Standard object list view |
8 | | - 'dashboard',// Dashboard page |
9 | | - 'page', // Custom page |
10 | | - 'url' // External link |
11 | | -]); |
12 | | - |
13 | | -/** |
14 | | - * Schema for App Navigation Items (Tabs) |
15 | | - */ |
16 | | -export const AppTabSchema = z.object({ |
17 | | - name: z.string().describe('Tab unique name'), |
18 | | - label: z.string().optional().describe('Override label'), |
19 | | - type: TabType.default('entity').describe('Tab type'), |
| 7 | +const BaseNavItemSchema = z.object({ |
| 8 | + /** Unique identifier for the item */ |
| 9 | + id: z.string().describe('Unique identifier for this navigation item'), |
20 | 10 |
|
21 | | - /** |
22 | | - * Reference ID based on type: |
23 | | - * - entity: entity_name |
24 | | - * - dashboard: dashboard_name |
25 | | - * - page: page_component_name |
26 | | - * - url: https://... |
27 | | - */ |
28 | | - reference: z.string().describe('Target reference ID'), |
| 11 | + /** Display label */ |
| 12 | + label: z.string().describe('Display proper label'), |
29 | 13 |
|
| 14 | + /** Icon name (Lucide) */ |
30 | 15 | icon: z.string().optional().describe('Icon name'), |
| 16 | + |
| 17 | + /** |
| 18 | + * Visibility condition. |
| 19 | + * Formula expression returning boolean. |
| 20 | + * e.g. "user.is_admin || user.department == 'sales'" |
| 21 | + */ |
| 22 | + visible: z.string().optional().describe('Visibility formula condition'), |
| 23 | +}); |
| 24 | + |
| 25 | +/** |
| 26 | + * 1. Object Navigation Item |
| 27 | + * Navigates to an object's list view. |
| 28 | + */ |
| 29 | +export const ObjectNavItemSchema = BaseNavItemSchema.extend({ |
| 30 | + type: z.literal('object'), |
| 31 | + objectName: z.string().describe('Target object name'), |
| 32 | + viewName: z.string().optional().describe('Default list view to open. Defaults to "all"'), |
| 33 | +}); |
| 34 | + |
| 35 | +/** |
| 36 | + * 2. Dashboard Navigation Item |
| 37 | + * Navigates to a specific dashboard. |
| 38 | + */ |
| 39 | +export const DashboardNavItemSchema = BaseNavItemSchema.extend({ |
| 40 | + type: z.literal('dashboard'), |
| 41 | + dashboardName: z.string().describe('Target dashboard name'), |
| 42 | +}); |
| 43 | + |
| 44 | +/** |
| 45 | + * 3. Page Navigation Item |
| 46 | + * Navigates to a custom UI page/component. |
| 47 | + */ |
| 48 | +export const PageNavItemSchema = BaseNavItemSchema.extend({ |
| 49 | + type: z.literal('page'), |
| 50 | + pageName: z.string().describe('Target custom page component name'), |
| 51 | + params: z.record(z.any()).optional().describe('Parameters passed to the page context'), |
| 52 | +}); |
| 53 | + |
| 54 | +/** |
| 55 | + * 4. URL Navigation Item |
| 56 | + * Navigates to an external or absolute URL. |
| 57 | + */ |
| 58 | +export const UrlNavItemSchema = BaseNavItemSchema.extend({ |
| 59 | + type: z.literal('url'), |
| 60 | + url: z.string().describe('Target external URL'), |
| 61 | + target: z.enum(['_self', '_blank']).default('_self').describe('Link target window'), |
| 62 | +}); |
| 63 | + |
| 64 | +/** |
| 65 | + * 5. Group Navigation Item |
| 66 | + * A container for child navigation items (Sub-menu). |
| 67 | + * Does not perform navigation itself. |
| 68 | + */ |
| 69 | +export const GroupNavItemSchema = BaseNavItemSchema.extend({ |
| 70 | + type: z.literal('group'), |
| 71 | + expanded: z.boolean().default(false).describe('Default expansion state in sidebar'), |
| 72 | + // children property is added in the recursive definition below |
| 73 | +}); |
| 74 | + |
| 75 | +/** |
| 76 | + * Recursive Union of all navigation item types. |
| 77 | + * Allows constructing a navigation tree. |
| 78 | + */ |
| 79 | +export const NavigationItemSchema: z.ZodType<any> = z.lazy(() => |
| 80 | + z.union([ |
| 81 | + ObjectNavItemSchema, |
| 82 | + DashboardNavItemSchema, |
| 83 | + PageNavItemSchema, |
| 84 | + UrlNavItemSchema, |
| 85 | + GroupNavItemSchema.extend({ |
| 86 | + children: z.array(NavigationItemSchema).describe('Child navigation items'), |
| 87 | + }) |
| 88 | + ]) |
| 89 | +); |
| 90 | + |
| 91 | +/** |
| 92 | + * App Branding Configuration |
| 93 | + * Allows configuring the look and feel of the specific app. |
| 94 | + */ |
| 95 | +export const AppBrandingSchema = z.object({ |
| 96 | + primaryColor: z.string().optional().describe('Primary theme color hex code'), |
| 97 | + logo: z.string().optional().describe('Custom logo URL for this app'), |
| 98 | + favicon: z.string().optional().describe('Custom favicon URL for this app'), |
31 | 99 | }); |
32 | 100 |
|
33 | 101 | /** |
34 | 102 | * Schema for Applications (Apps). |
35 | | - * An App is a container that groups tabs/entities for a specific business function. |
| 103 | + * A logical container for business functionality (e.g., "Sales CRM", "HR Portal"). |
36 | 104 | */ |
37 | 105 | export const AppSchema = z.object({ |
38 | | - /** Machine name */ |
| 106 | + /** Machine name (id) */ |
39 | 107 | name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('App unique machine name'), |
40 | 108 |
|
41 | | - /** Display label (e.g., "Sales CRM") */ |
| 109 | + /** Display label */ |
42 | 110 | label: z.string().describe('App display label'), |
43 | 111 |
|
44 | 112 | /** Description */ |
45 | 113 | description: z.string().optional().describe('App description'), |
46 | 114 |
|
47 | 115 | /** Icon name (Lucide) */ |
48 | | - icon: z.string().optional().describe('App icon'), |
| 116 | + icon: z.string().optional().describe('App icon used in the App Launcher'), |
49 | 117 |
|
50 | | - /** Active status */ |
| 118 | + /** Branding/Theming Configuration */ |
| 119 | + branding: AppBrandingSchema.optional().describe('App-specific branding'), |
| 120 | + |
| 121 | + /** Application status */ |
51 | 122 | active: z.boolean().default(true).describe('Whether the app is enabled'), |
| 123 | + |
| 124 | + /** Is this the default app for new users? */ |
| 125 | + isDefault: z.boolean().default(false).describe('Is default app'), |
52 | 126 |
|
53 | | - /** Ordered list of tabs/menu items */ |
54 | | - tabs: z.array(AppTabSchema).describe('Navigation structure'), |
| 127 | + /** |
| 128 | + * Navigation Tree Structure. |
| 129 | + * Replaces the old flat 'tabs' list with a structured menu. |
| 130 | + */ |
| 131 | + navigation: z.array(NavigationItemSchema).describe('Structured navigation menu tree'), |
55 | 132 |
|
56 | 133 | /** |
57 | | - * Profiles/Roles that can access this app. |
58 | | - * If empty, accessible to everyone (or controlled by other means). |
| 134 | + * App-level Home Page Override |
| 135 | + * ID of the navigation item to act as the landing page. |
| 136 | + * If not set, usually defaults to the first navigation item. |
59 | 137 | */ |
60 | | - profiles: z.array(z.string()).optional().describe('Profiles that can access this app'), |
| 138 | + homePageId: z.string().optional().describe('ID of the navigation item to serve as landing page'), |
| 139 | + |
| 140 | + /** |
| 141 | + * Access Control |
| 142 | + * List of permissions required to access this app. |
| 143 | + * Modern replacement for role/profile based assignment. |
| 144 | + * Example: ["app.access.crm"] |
| 145 | + */ |
| 146 | + requiredPermissions: z.array(z.string()).optional().describe('Permissions required to access this app'), |
61 | 147 | }); |
62 | 148 |
|
| 149 | +// Main Types |
63 | 150 | export type App = z.infer<typeof AppSchema>; |
64 | | -export type AppTab = z.infer<typeof AppTabSchema>; |
| 151 | +export type AppBranding = z.infer<typeof AppBrandingSchema>; |
| 152 | +export type NavigationItem = z.infer<typeof NavigationItemSchema>; |
| 153 | + |
| 154 | +// Discriminated Item Types (Helper exports) |
| 155 | +export type ObjectNavItem = z.infer<typeof ObjectNavItemSchema>; |
| 156 | +export type DashboardNavItem = z.infer<typeof DashboardNavItemSchema>; |
| 157 | +export type PageNavItem = z.infer<typeof PageNavItemSchema>; |
| 158 | +export type UrlNavItem = z.infer<typeof UrlNavItemSchema>; |
| 159 | +export type GroupNavItem = z.infer<typeof GroupNavItemSchema> & { children: NavigationItem[] }; |
0 commit comments