@axinom/mosaic-agent-skills 0.0.1
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/README.md +121 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/tools/graphql/index.d.ts +3 -0
- package/dist/tools/graphql/index.d.ts.map +1 -0
- package/dist/tools/graphql/index.js +84 -0
- package/dist/tools/graphql/index.js.map +1 -0
- package/dist/tools/graphql/tools.d.ts +71 -0
- package/dist/tools/graphql/tools.d.ts.map +1 -0
- package/dist/tools/graphql/tools.js +187 -0
- package/dist/tools/graphql/tools.js.map +1 -0
- package/dist/tools/graphql/utils.d.ts +20 -0
- package/dist/tools/graphql/utils.d.ts.map +1 -0
- package/dist/tools/graphql/utils.js +140 -0
- package/dist/tools/graphql/utils.js.map +1 -0
- package/dist/tools/skills/index.d.ts +3 -0
- package/dist/tools/skills/index.d.ts.map +1 -0
- package/dist/tools/skills/index.js +62 -0
- package/dist/tools/skills/index.js.map +1 -0
- package/dist/tools/skills/tools.d.ts +5 -0
- package/dist/tools/skills/tools.d.ts.map +1 -0
- package/dist/tools/skills/tools.js +67 -0
- package/dist/tools/skills/tools.js.map +1 -0
- package/dist/tools/skills/types.d.ts +12 -0
- package/dist/tools/skills/types.d.ts.map +1 -0
- package/dist/tools/skills/types.js +3 -0
- package/dist/tools/skills/types.js.map +1 -0
- package/dist/tools/skills/utils.d.ts +11 -0
- package/dist/tools/skills/utils.d.ts.map +1 -0
- package/dist/tools/skills/utils.js +127 -0
- package/dist/tools/skills/utils.js.map +1 -0
- package/package.json +40 -0
- package/skills/_shared/actions/execution-summary.md +53 -0
- package/skills/_shared/actions/typescript-codegen.md +32 -0
- package/skills/_shared/actions/typescript-validation.md +32 -0
- package/skills/_shared/conventions/field-component-mapping.md +155 -0
- package/skills/_shared/conventions/field-validation-schema.md +77 -0
- package/skills/_shared/discovery/discover-paths.md +52 -0
- package/skills/_shared/discovery/extract-entity-features.md +565 -0
- package/skills/generate-create-station/SKILL.md +93 -0
- package/skills/generate-create-station/refs/finalization/station-registration.md +163 -0
- package/skills/generate-create-station/refs/templates/create-form-station.md +206 -0
- package/skills/generate-create-station/refs/templates/graphql-operations.md +56 -0
- package/skills/generate-details-station/SKILL.md +125 -0
- package/skills/generate-details-station/refs/finalization/explorer-integration.md +242 -0
- package/skills/generate-details-station/refs/finalization/station-registration.md +139 -0
- package/skills/generate-details-station/refs/templates/actions.md +127 -0
- package/skills/generate-details-station/refs/templates/breadcrumb.md +67 -0
- package/skills/generate-details-station/refs/templates/details-form-station.md +589 -0
- package/skills/generate-details-station/refs/templates/details-wrapper.md +54 -0
- package/skills/generate-details-station/refs/templates/form-data-types.md +62 -0
- package/skills/generate-details-station/refs/templates/graphql-operations.md +256 -0
- package/skills/generate-details-station/refs/templates/managed-integrations/image-management-station.md +322 -0
- package/skills/generate-details-station/refs/templates/managed-integrations/video-management-station.md +283 -0
- package/skills/generate-explorer-station/SKILL.md +121 -0
- package/skills/generate-explorer-station/refs/finalization/station-registration.md +145 -0
- package/skills/generate-explorer-station/refs/select-columns-filters.md +63 -0
- package/skills/generate-explorer-station/refs/templates/bulk-edit.md +369 -0
- package/skills/generate-explorer-station/refs/templates/common/bulk-actions.md +64 -0
- package/skills/generate-explorer-station/refs/templates/common/columns.md +131 -0
- package/skills/generate-explorer-station/refs/templates/common/data-provider.md +83 -0
- package/skills/generate-explorer-station/refs/templates/common/filters.md +220 -0
- package/skills/generate-explorer-station/refs/templates/common/inline-actions.md +45 -0
- package/skills/generate-explorer-station/refs/templates/common/types.md +28 -0
- package/skills/generate-explorer-station/refs/templates/graphql-operations.md +235 -0
- package/skills/generate-explorer-station/refs/templates/navigation-explorer-station.md +144 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explorer-integration
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures
|
|
5
|
+
- Generated components (Details, Image Management, Video Management)
|
|
6
|
+
output:
|
|
7
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/{Entity}Details/{Entity}DetailsQuickEdit.tsx'
|
|
8
|
+
description: 'Details quick edit component'
|
|
9
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/{EntityPlural}Explorer/{EntityPlural}.tsx'
|
|
10
|
+
description:
|
|
11
|
+
'Explorer updated with quick edit registrations and inline actions'
|
|
12
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/{EntityPlural}Explorer/common/{EntityPlural}.inlineActions.ts'
|
|
13
|
+
description: 'Inline actions updated with Open Details and Delete actions'
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Integrate Details Station with Explorer
|
|
17
|
+
|
|
18
|
+
Creates quick edit wrapper components and fully integrates the details station
|
|
19
|
+
with the explorer by updating quick edit registrations, inline actions, and
|
|
20
|
+
navigation URLs.
|
|
21
|
+
|
|
22
|
+
## Step 1: Generate Details Quick Edit Component
|
|
23
|
+
|
|
24
|
+
**Path:** `{Entity}Details/{Entity}DetailsQuickEdit.tsx`
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui';
|
|
28
|
+
import React, { useContext } from 'react';
|
|
29
|
+
import { {Entity}Data } from '../{EntityPlural}Explorer/common/{EntityPlural}.types';
|
|
30
|
+
import { {Entity}DetailsForm } from './{Entity}DetailsForm';
|
|
31
|
+
|
|
32
|
+
export const {Entity}DetailsQuickEdit: React.FC = () => {
|
|
33
|
+
const { selectedItem } =
|
|
34
|
+
useContext<QuickEditContextType<{Entity}Data>>(QuickEditContext);
|
|
35
|
+
|
|
36
|
+
return <{Entity}DetailsForm {entityCamel}Id={selectedItem.id} />;
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Step 2: Check if Explorer Exists
|
|
41
|
+
|
|
42
|
+
**Discovery:** Check if `{EntityPlural}Explorer/{EntityPlural}.tsx` exists
|
|
43
|
+
|
|
44
|
+
- If **exists** → Proceed to Step 3
|
|
45
|
+
- If **does not exist** → Skip Step 3, move to next step
|
|
46
|
+
|
|
47
|
+
## Step 3: Integrate with Explorer
|
|
48
|
+
|
|
49
|
+
**File:** `{EntityPlural}Explorer/{EntityPlural}.tsx`
|
|
50
|
+
|
|
51
|
+
### 3.1: Add Imports
|
|
52
|
+
|
|
53
|
+
Add at the top of the file (after existing imports):
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { {Entity}DetailsQuickEdit } from '../{Entity}Details/{Entity}DetailsQuickEdit';
|
|
57
|
+
// If features.capabilities.images exists:
|
|
58
|
+
import { {Entity}ImageManagementQuickEdit } from '../{Entity}ImageManagement/{Entity}ImageManagementQuickEdit';
|
|
59
|
+
// End if
|
|
60
|
+
// If features.capabilities.videos exists:
|
|
61
|
+
import { {Entity}VideoManagementQuickEdit } from '../{Entity}VideoManagement/{Entity}VideoManagementQuickEdit';
|
|
62
|
+
// End if
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3.2: Update quickEditRegistrations
|
|
66
|
+
|
|
67
|
+
**Find** the `quickEditRegistrations` prop in the `NavigationExplorer`
|
|
68
|
+
component.
|
|
69
|
+
|
|
70
|
+
**Replace** the entire prop (including TODO comments) with:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
quickEditRegistrations={[
|
|
74
|
+
{ component: <{Entity}DetailsQuickEdit />, label: '{Entity} Details' },
|
|
75
|
+
// If features.capabilities.images exists:
|
|
76
|
+
{ component: <{Entity}ImageManagementQuickEdit />, label: 'Manage Images' },
|
|
77
|
+
// End if
|
|
78
|
+
// If features.capabilities.videos exists:
|
|
79
|
+
{ component: <{Entity}VideoManagementQuickEdit />, label: 'Manage Videos' },
|
|
80
|
+
// End if
|
|
81
|
+
]}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Before (with TODOs):**
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
quickEditRegistrations={[
|
|
88
|
+
// TODO: Add quick edit registrations once Details/Image/Video stations exist
|
|
89
|
+
// Example structure:
|
|
90
|
+
// { component: <{Entity}DetailsQuickEdit />, label: '{Entity} Details' },
|
|
91
|
+
// { component: <{Entity}ImageManagementQuickEdit />, label: 'Manage Images' },
|
|
92
|
+
// { component: <{Entity}VideoManagementQuickEdit />, label: 'Manage Videos' },
|
|
93
|
+
]}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**After (integrated):**
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
quickEditRegistrations={[
|
|
100
|
+
{ component: <MovieDetailsQuickEdit />, label: 'Movie Details' },
|
|
101
|
+
{ component: <MovieImageManagementQuickEdit />, label: 'Manage Images' },
|
|
102
|
+
{ component: <MovieVideoManagementQuickEdit />, label: 'Manage Videos' },
|
|
103
|
+
]}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3.3: Remove calculateNavigateUrl TODO
|
|
107
|
+
|
|
108
|
+
**Find** the `calculateNavigateUrl` prop.
|
|
109
|
+
|
|
110
|
+
**Remove** the TODO comment above it (if exists):
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Before:
|
|
114
|
+
// TODO: Update once Details station exists
|
|
115
|
+
calculateNavigateUrl={(item) => `/{kebabCasePlural}/${item.id}`}
|
|
116
|
+
|
|
117
|
+
// After:
|
|
118
|
+
calculateNavigateUrl={(item) => `/{kebabCasePlural}/${item.id}`}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3.4: Update Inline Actions
|
|
122
|
+
|
|
123
|
+
**File:** `{EntityPlural}Explorer/common/{EntityPlural}.inlineActions.ts`
|
|
124
|
+
|
|
125
|
+
Once the details station exists with delete mutation, update the inline actions
|
|
126
|
+
to enable "Open Details" navigation and "Delete" actions.
|
|
127
|
+
|
|
128
|
+
**Add imports** at the top:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { ActionData, IconName } from '@axinom/mosaic-ui';
|
|
132
|
+
import { client } from '{apolloClientPath}';
|
|
133
|
+
import { useDelete{Entity}Mutation } from '../../../../generated/graphql';
|
|
134
|
+
import { {Entity}Data } from './{EntityPlural}.types';
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Replace** the TODO placeholders in the hook with actual implementations:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
export function use{EntityPlural}InlineActions(): {
|
|
141
|
+
readonly generateInlineMenuActions: (data: {Entity}Data) => ActionData[];
|
|
142
|
+
} {
|
|
143
|
+
const [delete{Entity}Mutation] = useDelete{Entity}Mutation({
|
|
144
|
+
client,
|
|
145
|
+
fetchPolicy: 'no-cache',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const generateInlineMenuActions = ({ id }: {Entity}Data): ActionData[] => {
|
|
149
|
+
return [
|
|
150
|
+
{
|
|
151
|
+
label: 'Open Details',
|
|
152
|
+
path: `/{kebabCasePlural}/${id}`,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
label: 'Delete',
|
|
156
|
+
onActionSelected: async () => {
|
|
157
|
+
await delete{Entity}Mutation({ variables: { input: { id } } });
|
|
158
|
+
},
|
|
159
|
+
icon: IconName.Delete,
|
|
160
|
+
confirmationMode: 'Simple',
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return { generateInlineMenuActions };
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Example (Complete Integration)
|
|
170
|
+
|
|
171
|
+
**MovieDetailsQuickEdit.tsx:**
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui';
|
|
175
|
+
import React, { useContext } from 'react';
|
|
176
|
+
import { MovieData } from '../MoviesExplorer/common/Movies.types';
|
|
177
|
+
import { MovieDetailsForm } from './MovieDetailsForm';
|
|
178
|
+
|
|
179
|
+
export const MovieDetailsQuickEdit: React.FC = () => {
|
|
180
|
+
const { selectedItem } =
|
|
181
|
+
useContext<QuickEditContextType<MovieData>>(QuickEditContext);
|
|
182
|
+
|
|
183
|
+
return <MovieDetailsForm movieId={selectedItem.id} />;
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Movies.tsx (Explorer) - Updated:**
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { NavigationExplorer } from '@axinom/mosaic-ui';
|
|
191
|
+
import React from 'react';
|
|
192
|
+
import { MovieDetailsQuickEdit } from '../MovieDetails/MovieDetailsQuickEdit';
|
|
193
|
+
import { MovieImageManagementQuickEdit } from '../MovieImageManagement/MovieImageManagementQuickEdit';
|
|
194
|
+
import { MovieVideoManagementQuickEdit } from '../MovieVideoManagement/MovieVideoManagementQuickEdit';
|
|
195
|
+
import { useMoviesBulkActions } from './common/Movies.bulkActions';
|
|
196
|
+
import { moviesColumns } from './common/Movies.columns';
|
|
197
|
+
import { createMoviesDataProvider } from './common/Movies.dataProvider';
|
|
198
|
+
import { useMoviesFilters } from './common/Movies.filters';
|
|
199
|
+
import { useMoviesInlineActions } from './common/Movies.inlineActions';
|
|
200
|
+
import { MovieData } from './common/Movies.types';
|
|
201
|
+
|
|
202
|
+
export const Movies: React.FC = () => {
|
|
203
|
+
const { transformFilters, filterOptions } = useMoviesFilters();
|
|
204
|
+
const { bulkActions } = useMoviesBulkActions();
|
|
205
|
+
const { generateInlineMenuActions } = useMoviesInlineActions();
|
|
206
|
+
const dataProvider = createMoviesDataProvider(transformFilters);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<NavigationExplorer<MovieData>
|
|
210
|
+
title="Movies"
|
|
211
|
+
stationKey="MoviesExplorer"
|
|
212
|
+
calculateNavigateUrl={(item) => `/movies/${item.id}`}
|
|
213
|
+
onCreateAction="/movies/create"
|
|
214
|
+
columns={moviesColumns}
|
|
215
|
+
dataProvider={dataProvider}
|
|
216
|
+
filterOptions={filterOptions}
|
|
217
|
+
defaultSortOrder={{ column: 'updatedDate', direction: 'desc' }}
|
|
218
|
+
inlineMenuActions={generateInlineMenuActions}
|
|
219
|
+
bulkActions={bulkActions}
|
|
220
|
+
quickEditRegistrations={[
|
|
221
|
+
{ component: <MovieDetailsQuickEdit />, label: 'Movie Details' },
|
|
222
|
+
{
|
|
223
|
+
component: <MovieImageManagementQuickEdit />,
|
|
224
|
+
label: 'Manage Images',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
component: <MovieVideoManagementQuickEdit />,
|
|
228
|
+
label: 'Manage Videos',
|
|
229
|
+
},
|
|
230
|
+
]}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Notes
|
|
237
|
+
|
|
238
|
+
- Quick edit components reuse the same form components as full-page views
|
|
239
|
+
- Explorer must exist for integration; if not found, only generate the component
|
|
240
|
+
- All quick edit components follow same pattern (context → extract ID → render
|
|
241
|
+
form)
|
|
242
|
+
- Quick edit registrations appear in order: Details, Images, Videos
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: station-registration
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures
|
|
5
|
+
- Generated components
|
|
6
|
+
- DiscoveredPaths
|
|
7
|
+
output:
|
|
8
|
+
- Updated registrations.tsx with details routes
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Register Details Station
|
|
12
|
+
|
|
13
|
+
Update entity registrations file with details, image management, and video
|
|
14
|
+
management routes.
|
|
15
|
+
|
|
16
|
+
## File Path
|
|
17
|
+
|
|
18
|
+
`{workflowsPath}/src/Stations/{EntityPlural}/registrations.tsx`
|
|
19
|
+
|
|
20
|
+
## Add Imports
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { {Entity}Details } from './{Entity}Details/{Entity}Details';
|
|
24
|
+
import { {Entity}DetailsCrumb } from './{Entity}Details/{Entity}DetailsCrumb';
|
|
25
|
+
// If features.capabilities.images exists:
|
|
26
|
+
import { {Entity}ImageManagement } from './{Entity}ImageManagement/{Entity}ImageManagement';
|
|
27
|
+
// End if
|
|
28
|
+
// If features.capabilities.videos exists:
|
|
29
|
+
import { {Entity}VideoManagement } from './{Entity}VideoManagement/{Entity}VideoManagement';
|
|
30
|
+
// End if
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Add Route Registrations
|
|
34
|
+
|
|
35
|
+
Add inside the `register()` function:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Details route
|
|
39
|
+
app.registerPage(
|
|
40
|
+
'/{kebabCasePlural}/:{entityCamel}Id',
|
|
41
|
+
() => (
|
|
42
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
43
|
+
<{Entity}Details />
|
|
44
|
+
</ExtensionsContext.Provider>
|
|
45
|
+
),
|
|
46
|
+
{
|
|
47
|
+
breadcrumb: {Entity}DetailsCrumb,
|
|
48
|
+
permissions: { '{features.service.serviceId}': {features.service.permissions} },
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// If features.capabilities.images exists:
|
|
53
|
+
// Image management route
|
|
54
|
+
app.registerPage(
|
|
55
|
+
'/{kebabCasePlural}/:{entityCamel}Id/images',
|
|
56
|
+
() => (
|
|
57
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
58
|
+
<{Entity}ImageManagement />
|
|
59
|
+
</ExtensionsContext.Provider>
|
|
60
|
+
),
|
|
61
|
+
{
|
|
62
|
+
breadcrumb: () => 'Manage Images',
|
|
63
|
+
permissions: { '{features.service.serviceId}': {features.service.permissions} },
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
// End if
|
|
67
|
+
|
|
68
|
+
// If features.capabilities.videos exists:
|
|
69
|
+
// Video management route
|
|
70
|
+
app.registerPage(
|
|
71
|
+
'/{kebabCasePlural}/:{entityCamel}Id/videos',
|
|
72
|
+
() => (
|
|
73
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
74
|
+
<{Entity}VideoManagement />
|
|
75
|
+
</ExtensionsContext.Provider>
|
|
76
|
+
),
|
|
77
|
+
{
|
|
78
|
+
breadcrumb: () => 'Manage Videos',
|
|
79
|
+
permissions: { '{features.service.serviceId}': {features.service.permissions} },
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
// End if
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Example
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// In registrations.tsx
|
|
89
|
+
import { MovieDetails } from './MovieDetails/MovieDetails';
|
|
90
|
+
import { MovieDetailsCrumb } from './MovieDetails/MovieDetailsCrumb';
|
|
91
|
+
import { MovieImageManagement } from './MovieImageManagement/MovieImageManagement';
|
|
92
|
+
import { MovieVideoManagement } from './MovieVideoManagement/MovieVideoManagement';
|
|
93
|
+
|
|
94
|
+
export function register(app: PiletApi, extensions: Extensions): void {
|
|
95
|
+
// ... existing explorer registration ...
|
|
96
|
+
|
|
97
|
+
// Details route
|
|
98
|
+
app.registerPage(
|
|
99
|
+
'/movies/:movieId',
|
|
100
|
+
() => (
|
|
101
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
102
|
+
<MovieDetails />
|
|
103
|
+
</ExtensionsContext.Provider>
|
|
104
|
+
),
|
|
105
|
+
{
|
|
106
|
+
breadcrumb: MovieDetailsCrumb,
|
|
107
|
+
permissions: { 'media-service': ['ADMIN', 'MOVIES_EDIT', 'MOVIES_VIEW'] },
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Image management route
|
|
112
|
+
app.registerPage(
|
|
113
|
+
'/movies/:movieId/images',
|
|
114
|
+
() => (
|
|
115
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
116
|
+
<MovieImageManagement />
|
|
117
|
+
</ExtensionsContext.Provider>
|
|
118
|
+
),
|
|
119
|
+
{
|
|
120
|
+
breadcrumb: () => 'Manage Images',
|
|
121
|
+
permissions: { 'media-service': ['ADMIN', 'MOVIES_EDIT', 'MOVIES_VIEW'] },
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Video management route
|
|
126
|
+
app.registerPage(
|
|
127
|
+
'/movies/:movieId/videos',
|
|
128
|
+
() => (
|
|
129
|
+
<ExtensionsContext.Provider value={extensions}>
|
|
130
|
+
<MovieVideoManagement />
|
|
131
|
+
</ExtensionsContext.Provider>
|
|
132
|
+
),
|
|
133
|
+
{
|
|
134
|
+
breadcrumb: () => 'Manage Videos',
|
|
135
|
+
permissions: { 'media-service': ['ADMIN', 'MOVIES_EDIT', 'MOVIES_VIEW'] },
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: details-actions
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures
|
|
5
|
+
- DiscoveredPaths
|
|
6
|
+
output:
|
|
7
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/{Entity}Details/{Entity}Details.actions.ts'
|
|
8
|
+
description: 'Actions hook with navigation and mutation actions'
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Generate Actions Hook
|
|
12
|
+
|
|
13
|
+
Provides action buttons for details form (delete, navigate to child resources).
|
|
14
|
+
|
|
15
|
+
## Template
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { FormActionData } from '@axinom/mosaic-ui';
|
|
19
|
+
import { useHistory } from 'react-router';
|
|
20
|
+
import { client } from '../../../apolloClient';
|
|
21
|
+
import {
|
|
22
|
+
// If features.mutations.delete === true:
|
|
23
|
+
useDelete{Entity}Mutation,
|
|
24
|
+
// End if
|
|
25
|
+
} from '../../../generated/graphql';
|
|
26
|
+
import { {Entity}DetailsFormData } from './{Entity}Details.types';
|
|
27
|
+
|
|
28
|
+
export function use{Entity}DetailsActions(id: number): {
|
|
29
|
+
readonly actions: FormActionData<{Entity}DetailsFormData}[];
|
|
30
|
+
} {
|
|
31
|
+
const history = useHistory();
|
|
32
|
+
// If features.mutations.delete === true:
|
|
33
|
+
const [delete{Entity}Mutation] = useDelete{Entity}Mutation({
|
|
34
|
+
client,
|
|
35
|
+
fetchPolicy: 'no-cache',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const delete{Entity} = async (): Promise<void> => {
|
|
39
|
+
await delete{Entity}Mutation({ variables: { input: { id } } });
|
|
40
|
+
history.push('/{kebabCasePlural}');
|
|
41
|
+
};
|
|
42
|
+
// End if
|
|
43
|
+
|
|
44
|
+
const actions: FormActionData<{Entity}DetailsFormData}[] = [
|
|
45
|
+
// If features.capabilities.videos exists:
|
|
46
|
+
{
|
|
47
|
+
label: 'Manage Videos',
|
|
48
|
+
path: `/{kebabCasePlural}/${id}/videos`,
|
|
49
|
+
},
|
|
50
|
+
// End if
|
|
51
|
+
|
|
52
|
+
// If features.capabilities.images exists:
|
|
53
|
+
{
|
|
54
|
+
label: 'Manage Images',
|
|
55
|
+
path: `/{kebabCasePlural}/${id}/images`,
|
|
56
|
+
},
|
|
57
|
+
// End if
|
|
58
|
+
|
|
59
|
+
// If features.mutations.delete === true:
|
|
60
|
+
{
|
|
61
|
+
label: 'Delete',
|
|
62
|
+
confirmationMode: 'Simple' as const,
|
|
63
|
+
onActionSelected: delete{Entity},
|
|
64
|
+
},
|
|
65
|
+
// End if
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return { actions } as const;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Action Types
|
|
73
|
+
|
|
74
|
+
**Navigation Actions** (`path` property):
|
|
75
|
+
|
|
76
|
+
- Manage Videos (if `features.capabilities.videos` exists)
|
|
77
|
+
- Manage Images (if `features.capabilities.images` exists)
|
|
78
|
+
|
|
79
|
+
**Mutation Actions** (`onActionSelected` property):
|
|
80
|
+
|
|
81
|
+
- Delete (if `features.mutations.delete === true`)
|
|
82
|
+
- Requires `confirmationMode: 'Simple'`
|
|
83
|
+
- Navigates to list view after successful deletion
|
|
84
|
+
|
|
85
|
+
## Example
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { FormActionData } from '@axinom/mosaic-ui';
|
|
89
|
+
import { useHistory } from 'react-router';
|
|
90
|
+
import { client } from '../../../apolloClient';
|
|
91
|
+
import { useDeleteMovieMutation } from '../../../generated/graphql';
|
|
92
|
+
import { MovieDetailsFormData } from './MovieDetails.types';
|
|
93
|
+
|
|
94
|
+
export function useMovieDetailsActions(id: number): {
|
|
95
|
+
readonly actions: FormActionData<MovieDetailsFormData>[];
|
|
96
|
+
} {
|
|
97
|
+
const history = useHistory();
|
|
98
|
+
|
|
99
|
+
const [deleteMovieMutation] = useDeleteMovieMutation({
|
|
100
|
+
client,
|
|
101
|
+
fetchPolicy: 'no-cache',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const deleteMovie = async (): Promise<void> => {
|
|
105
|
+
await deleteMovieMutation({ variables: { input: { id } } });
|
|
106
|
+
history.push('/movies');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const actions: FormActionData<MovieDetailsFormData>[] = [
|
|
110
|
+
{
|
|
111
|
+
label: 'Manage Videos',
|
|
112
|
+
path: `/movies/${id}/videos`,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
label: 'Manage Images',
|
|
116
|
+
path: `/movies/${id}/images`,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: 'Delete',
|
|
120
|
+
confirmationMode: 'Simple' as const,
|
|
121
|
+
onActionSelected: deleteMovie,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
return { actions } as const;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: details-breadcrumb
|
|
3
|
+
input:
|
|
4
|
+
- EntityFeatures
|
|
5
|
+
output:
|
|
6
|
+
- path: '{workflowsPath}/src/Stations/{EntityPlural}/{Entity}Details/{Entity}DetailsCrumb.tsx'
|
|
7
|
+
description: 'Async breadcrumb resolver'
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Generate Breadcrumb Resolver
|
|
11
|
+
|
|
12
|
+
Fetches entity title for breadcrumb display.
|
|
13
|
+
|
|
14
|
+
## Template
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { BreadcrumbResolver } from '@axinom/mosaic-portal';
|
|
18
|
+
import { client } from '../../../apolloClient';
|
|
19
|
+
import {
|
|
20
|
+
{Entity}TitleDocument,
|
|
21
|
+
{Entity}TitleQuery,
|
|
22
|
+
} from '../../../generated/graphql';
|
|
23
|
+
|
|
24
|
+
export const {Entity}DetailsCrumb: BreadcrumbResolver = (params) => {
|
|
25
|
+
return async (): Promise<string> => {
|
|
26
|
+
const response = await client.query<{Entity}TitleQuery>({
|
|
27
|
+
query: {Entity}TitleDocument,
|
|
28
|
+
variables: {
|
|
29
|
+
id: Number(params['{entityCamel}Id']),
|
|
30
|
+
},
|
|
31
|
+
errorPolicy: 'ignore',
|
|
32
|
+
});
|
|
33
|
+
return response.data.{entityCamel}?.{titleField} ?? '{Entity} Details';
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- Extracts `{entityCamel}Id` from route params
|
|
41
|
+
- Queries `{titleField}` from entity
|
|
42
|
+
- Falls back to `'{Entity} Details'` if entity not found
|
|
43
|
+
- Uses `errorPolicy: 'ignore'` to handle deleted/missing entities gracefully
|
|
44
|
+
|
|
45
|
+
## Example
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { BreadcrumbResolver } from '@axinom/mosaic-portal';
|
|
49
|
+
import { client } from '../../../apolloClient';
|
|
50
|
+
import {
|
|
51
|
+
MovieTitleDocument,
|
|
52
|
+
MovieTitleQuery,
|
|
53
|
+
} from '../../../generated/graphql';
|
|
54
|
+
|
|
55
|
+
export const MovieDetailsCrumb: BreadcrumbResolver = (params) => {
|
|
56
|
+
return async (): Promise<string> => {
|
|
57
|
+
const response = await client.query<MovieTitleQuery>({
|
|
58
|
+
query: MovieTitleDocument,
|
|
59
|
+
variables: {
|
|
60
|
+
id: Number(params['movieId']),
|
|
61
|
+
},
|
|
62
|
+
errorPolicy: 'ignore',
|
|
63
|
+
});
|
|
64
|
+
return response.data.movie?.title ?? 'Movie Details';
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
```
|