From ebf54207e32ca6d9f573ed08caf77378f069a639 Mon Sep 17 00:00:00 2001 From: Isuru <92033440+axisuru@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:11:40 +0530 Subject: [PATCH] feat: added quick edits for all media workflows (#362) --- .../CollectionDetails/CollectionDetails.tsx | 265 +---------------- .../CollectionDetailsForm.tsx | 271 ++++++++++++++++++ .../CollectionDetailsQuickEdit.tsx | 13 + .../CollectionEntityManagement.tsx | 130 +-------- .../CollectionEntityManagementForm.tsx | 136 +++++++++ .../CollectionEntityManagementQuickEdit.tsx | 13 + .../CollectionImageManagement.tsx | 216 +------------- .../CollectionImageManagementForm.tsx | 222 ++++++++++++++ .../CollectionImageManagementQuickEdit.tsx | 13 + .../CollectionsExplorer/Collections.tsx | 19 ++ .../Stations/Collections/registrations.tsx | 18 +- .../EpisodeExplorerBase/EpisodeExplorer.tsx | 7 - .../EpisodeImageManagement.tsx | 213 +------------- .../EpisodeImageManagementForm.tsx | 219 ++++++++++++++ .../EpisodeImageManagementQuickEdit.tsx | 13 + .../EpisodeVideoManagement.tsx | 138 +-------- .../EpisodeVideoManagementForm.tsx | 144 ++++++++++ .../EpisodeVideoManagementQuickEdit.tsx | 13 + .../Episodes/EpisodesExplorer/Episodes.tsx | 19 ++ .../src/Stations/Episodes/registrations.tsx | 18 +- .../MovieExplorerBase/MovieExplorer.tsx | 4 - .../MovieImageManagement.tsx | 214 +------------- .../MovieImageManagementForm.tsx | 219 ++++++++++++++ .../MovieImageManagementQuickEdit.tsx | 13 + .../MovieVideoManagement.tsx | 133 +-------- .../MovieVideoManagementForm.tsx | 139 +++++++++ .../MovieVideoManagementQuickEdit.tsx | 13 + .../Stations/Movies/MoviesExplorer/Movies.tsx | 16 ++ .../src/Stations/Movies/registrations.tsx | 16 +- .../SeasonEpisodeManagement.tsx | 103 +------ .../SeasonEpisodeManagementForm.tsx | 101 +++++++ .../SeasonEpisodeManagementQuickEdit.tsx | 13 + .../SeasonExplorerBase/SeasonExplorer.tsx | 4 - .../SeasonImageManagement.tsx | 221 +------------- .../SeasonImageManagementForm.tsx | 219 ++++++++++++++ .../SeasonImageManagementQuickEdit.tsx | 13 + .../SeasonVideoManagement.tsx | 116 +------- .../SeasonVideoManagementForm.tsx | 114 ++++++++ .../SeasonVideoManagementQuickEdit.tsx | 13 + .../Seasons/SeasonsExplorer/Seasons.tsx | 22 ++ .../src/Stations/Seasons/registrations.tsx | 18 +- .../TvShowExplorerBase/TvShowExplorer.tsx | 4 - .../TvShowImageManagement.tsx | 213 +------------- .../TvShowImageManagementForm.tsx | 219 ++++++++++++++ .../TvShowImageManagementQuickEdit.tsx | 13 + .../TvShowSeasonManagement.tsx | 93 +----- .../TvShowSeasonManagementForm.tsx | 99 +++++++ .../TvShowSeasonManagementQuickEdit.tsx | 13 + .../TvShowVideoManagement.tsx | 109 +------ .../TvShowVideoManagementForm.tsx | 114 ++++++++ .../TvShowVideoManagementQuickEdit.tsx | 13 + .../TvShows/TvShowsExplorer/TvShows.tsx | 22 ++ .../src/Stations/TvShows/registrations.tsx | 18 +- 53 files changed, 2598 insertions(+), 2156 deletions(-) create mode 100644 services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsForm.tsx create mode 100644 services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementQuickEdit.tsx create mode 100644 services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementForm.tsx create mode 100644 services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementQuickEdit.tsx diff --git a/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetails.tsx b/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetails.tsx index 6b926787..0358f357 100644 --- a/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetails.tsx +++ b/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetails.tsx @@ -1,52 +1,6 @@ -import { ID } from '@axinom/mosaic-managed-workflow-integration'; -import { - createUpdateGQLFragmentGenerator, - CustomTagsField, - Details, - DetailsProps, - formatDateTime, - generateArrayMutations, - getFormDiff, - InfoPanel, - ObjectSchemaDefinition, - Paragraph, - Section, - SingleLineTextField, - TextAreaField, -} from '@axinom/mosaic-ui'; -import { Field, useFormikContext } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback, useContext, useMemo } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - CollectionDocument, - CollectionImageType, - CollectionQuery, - Mutation, - MutationCreateCollectionsTagArgs, - MutationDeleteCollectionsTagArgs, - SearchCollectionTagsDocument, - SearchCollectionTagsQuery, - SearchCollectionTagsQueryVariables, - UpdateCollectionInput, - useCollectionQuery, -} from '../../../generated/graphql'; -import { getEnumLabel } from '../../../Util/StringEnumMapper/StringEnumMapper'; -import { useCollectionDetailsActions } from './CollectionDetails.actions'; -import classes from './CollectionDetails.module.scss'; -import { CollectionDetailsFormData } from './CollectionDetails.types'; - -const collectionDetailSchema = Yup.object().shape< - ObjectSchemaDefinition ->({ - title: Yup.string().required('Title is a required field').max(100), - description: Yup.string().nullable(), - synopsis: Yup.string().nullable(), - externalId: Yup.string().nullable(), -}); +import { CollectionDetailsForm } from './CollectionDetailsForm'; export const CollectionDetails: React.FC = () => { const collectionId = Number( @@ -55,218 +9,5 @@ export const CollectionDetails: React.FC = () => { }>().collectionId, ); - const { loading, data, error } = useCollectionQuery({ - client, - variables: { id: collectionId }, - fetchPolicy: 'network-only', - }); - - const { tags } = useMemo( - () => ({ - tags: data?.collection?.collectionsTags.nodes.map((node) => node.name), - }), - [data], - ); - - const { actions } = useCollectionDetailsActions(collectionId); - - const onSubmit = useCallback( - async ( - formData: CollectionDetailsFormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const tagAssignmentMutations = generateArrayMutations({ - current: formData.tags, - original: initialData.data?.tags, - generateCreateMutation: (name) => - generateUpdateGQLFragment( - 'createCollectionsTag', - { input: { collectionsTag: { name, collectionId } } }, - ), - generateDeleteMutation: (name) => - generateUpdateGQLFragment( - 'deleteCollectionsTag', - { input: { collectionId, name } }, - ), - prefix: 'collectionsTag', - }); - - const patch = createUpdateDto(formData, initialData.data); - - const GqlMutationDocument = gql`mutation UpdateCollection($input: UpdateCollectionInput!) { - updateCollection(input: $input) { - clientMutationId - collection { - id - title - } - } - ${tagAssignmentMutations} - }`; - - await client.mutate({ - mutation: GqlMutationDocument, - variables: { input: { id: collectionId, patch } }, - refetchQueries: [CollectionDocument], - awaitRefetchQueries: true, - }); - }, - [collectionId], - ); - - return ( - - defaultTitle="Collection" - titleProperty="title" - subtitle="Properties" - alwaysShowActionsPanel={true} - actions={actions} - validationSchema={collectionDetailSchema} - initialData={{ - data: { - ...data?.collection, - tags, - }, - loading, - entityNotFound: data?.collection === null, - error: error?.message, - }} - saveData={onSubmit} - infoPanel={} - > -
- - ); -}; - -const Panel: React.FC = () => { - const { ImageCover } = useContext(ExtensionsContext); - const { values } = - useFormikContext>(); - - return useMemo(() => { - let coverImageId: ID; - let coverImageCount = 0; - - values.collectionsImages?.nodes.forEach(({ imageId, imageType }) => { - switch (imageType) { - case CollectionImageType.Cover: - coverImageCount++; - coverImageId = imageId; - break; - default: - break; - } - }); - - return ( - -
- -
-
- {values.id} - - {formatDateTime(values.createdDate)} by {values.createdUser} - - - {formatDateTime(values.updatedDate)} by {values.updatedUser} - - - {getEnumLabel(values.publishStatus)} - - {values.publishedDate ? ( - - {formatDateTime(values.publishedDate)} by {values.publishedUser} - - ) : null} -
-
- -
-
Movies
-
- {values.movies?.totalCount} / many -
-
TV Shows
-
- {values.tvshows?.totalCount} / many -
-
Seasons
-
- {values.seasons?.totalCount} / many -
-
Episodes
-
- {values.episodes?.totalCount} / many -
-
-
- -
-
Cover
-
- {coverImageCount} / 1 -
-
-
-
-
- ); - }, [ - values.collectionsImages?.nodes, - values.id, - values.createdDate, - values.createdUser, - values.updatedDate, - values.updatedUser, - values.movies?.totalCount, - values.tvshows?.totalCount, - values.seasons?.totalCount, - values.episodes?.totalCount, - values.publishStatus, - values.publishedDate, - values.publishedUser, - ImageCover, - ]); + return ; }; - -const Form: React.FC = () => { - const tagsResolver = async (value: string): Promise<(string | null)[]> => { - const { data } = await client.query< - SearchCollectionTagsQuery, - SearchCollectionTagsQueryVariables - >({ - query: SearchCollectionTagsDocument, - variables: { searchKey: value, limit: 10 }, - }); - return data.getCollectionsTagsValues?.nodes ?? []; - }; - - return ( - <> - - - - - - - ); -}; - -function createUpdateDto( - currentValues: CollectionDetailsFormData, - initialValues?: CollectionDetailsFormData | null, -): CollectionDetailsFormData { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { tags, ...rest } = getFormDiff(currentValues, initialValues); - return rest; -} diff --git a/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsForm.tsx b/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsForm.tsx new file mode 100644 index 00000000..06bae031 --- /dev/null +++ b/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsForm.tsx @@ -0,0 +1,271 @@ +import { ID } from '@axinom/mosaic-managed-workflow-integration'; +import { + createUpdateGQLFragmentGenerator, + CustomTagsField, + Details, + DetailsProps, + formatDateTime, + generateArrayMutations, + getFormDiff, + InfoPanel, + ObjectSchemaDefinition, + Paragraph, + Section, + SingleLineTextField, + TextAreaField, +} from '@axinom/mosaic-ui'; +import { Field, useFormikContext } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext, useMemo } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + CollectionDocument, + CollectionImageType, + CollectionQuery, + Mutation, + MutationCreateCollectionsTagArgs, + MutationDeleteCollectionsTagArgs, + SearchCollectionTagsDocument, + SearchCollectionTagsQuery, + SearchCollectionTagsQueryVariables, + UpdateCollectionInput, + useCollectionQuery, +} from '../../../generated/graphql'; +import { getEnumLabel } from '../../../Util/StringEnumMapper/StringEnumMapper'; +import { useCollectionDetailsActions } from './CollectionDetails.actions'; +import classes from './CollectionDetails.module.scss'; +import { CollectionDetailsFormData } from './CollectionDetails.types'; + +interface CollectionDetailsFormProps { + collectionId: number; +} + +const collectionDetailSchema = Yup.object().shape< + ObjectSchemaDefinition +>({ + title: Yup.string().required('Title is a required field').max(100), + description: Yup.string().nullable(), + synopsis: Yup.string().nullable(), + externalId: Yup.string().nullable(), +}); + +export const CollectionDetailsForm: React.FC = ({ + collectionId, +}) => { + const { loading, data, error } = useCollectionQuery({ + client, + variables: { id: collectionId }, + fetchPolicy: 'network-only', + }); + + const { tags } = useMemo( + () => ({ + tags: data?.collection?.collectionsTags.nodes.map((node) => node.name), + }), + [data], + ); + + const { actions } = useCollectionDetailsActions(collectionId); + + const onSubmit = useCallback( + async ( + formData: CollectionDetailsFormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const tagAssignmentMutations = generateArrayMutations({ + current: formData.tags, + original: initialData.data?.tags, + generateCreateMutation: (name) => + generateUpdateGQLFragment( + 'createCollectionsTag', + { input: { collectionsTag: { name, collectionId } } }, + ), + generateDeleteMutation: (name) => + generateUpdateGQLFragment( + 'deleteCollectionsTag', + { input: { collectionId, name } }, + ), + prefix: 'collectionsTag', + }); + + const patch = createUpdateDto(formData, initialData.data); + + const GqlMutationDocument = gql`mutation UpdateCollection($input: UpdateCollectionInput!) { + updateCollection(input: $input) { + clientMutationId + collection { + id + title + } + } + ${tagAssignmentMutations} + }`; + + await client.mutate({ + mutation: GqlMutationDocument, + variables: { input: { id: collectionId, patch } }, + refetchQueries: [CollectionDocument], + awaitRefetchQueries: true, + }); + }, + [collectionId], + ); + + return ( + + defaultTitle="Collection" + titleProperty="title" + subtitle="Properties" + alwaysShowActionsPanel={true} + actions={actions} + validationSchema={collectionDetailSchema} + initialData={{ + data: { + ...data?.collection, + tags, + }, + loading, + entityNotFound: data?.collection === null, + error: error?.message, + }} + saveData={onSubmit} + infoPanel={} + > + + + ); +}; + +const Panel: React.FC = () => { + const { ImageCover } = useContext(ExtensionsContext); + const { values } = + useFormikContext>(); + + return useMemo(() => { + let coverImageId: ID; + let coverImageCount = 0; + + values.collectionsImages?.nodes.forEach(({ imageId, imageType }) => { + switch (imageType) { + case CollectionImageType.Cover: + coverImageCount++; + coverImageId = imageId; + break; + default: + break; + } + }); + + return ( + +
+ +
+
+ {values.id} + + {formatDateTime(values.createdDate)} by {values.createdUser} + + + {formatDateTime(values.updatedDate)} by {values.updatedUser} + + + {getEnumLabel(values.publishStatus)} + + {values.publishedDate ? ( + + {formatDateTime(values.publishedDate)} by {values.publishedUser} + + ) : null} +
+
+ +
+
Movies
+
+ {values.movies?.totalCount} / many +
+
TV Shows
+
+ {values.tvshows?.totalCount} / many +
+
Seasons
+
+ {values.seasons?.totalCount} / many +
+
Episodes
+
+ {values.episodes?.totalCount} / many +
+
+
+ +
+
Cover
+
+ {coverImageCount} / 1 +
+
+
+
+
+ ); + }, [ + values.collectionsImages?.nodes, + values.id, + values.createdDate, + values.createdUser, + values.updatedDate, + values.updatedUser, + values.movies?.totalCount, + values.tvshows?.totalCount, + values.seasons?.totalCount, + values.episodes?.totalCount, + values.publishStatus, + values.publishedDate, + values.publishedUser, + ImageCover, + ]); +}; + +const Form: React.FC = () => { + const tagsResolver = async (value: string): Promise<(string | null)[]> => { + const { data } = await client.query< + SearchCollectionTagsQuery, + SearchCollectionTagsQueryVariables + >({ + query: SearchCollectionTagsDocument, + variables: { searchKey: value, limit: 10 }, + }); + return data.getCollectionsTagsValues?.nodes ?? []; + }; + + return ( + <> + + + + + + + ); +}; + +function createUpdateDto( + currentValues: CollectionDetailsFormData, + initialValues?: CollectionDetailsFormData | null, +): CollectionDetailsFormData { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { tags, ...rest } = getFormDiff(currentValues, initialValues); + return rest; +} diff --git a/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsQuickEdit.tsx b/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsQuickEdit.tsx new file mode 100644 index 00000000..581dfd70 --- /dev/null +++ b/services/media/workflows/src/Stations/Collections/CollectionDetails/CollectionDetailsQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { CollectionData } from '../CollectionsExplorer/Collections.types'; +import { CollectionDetailsForm } from './CollectionDetailsForm'; + +export const CollectionDetailsQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagement.tsx b/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagement.tsx index 71edca9e..6440dcea 100644 --- a/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagement.tsx +++ b/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagement.tsx @@ -1,36 +1,6 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutationsWithUpdates, - ObjectSchemaDefinition, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { - EntityType, - Mutation, - MutationCreateCollectionRelationArgs, - MutationDeleteCollectionRelationArgs, - MutationUpdateCollectionRelationArgs, -} from '../../../generated/graphql'; -import { CollectionRelatedEntity } from './CollectionEntityManagement.types'; -import { useCollectionRelatedEntities } from './CollectionEntityRelationMapper/CollectionEntityRelationMapper'; -import { EntitySelectField } from './EntitySelectField/EntitySelectField'; - -interface FormData { - entities: CollectionRelatedEntity[] | undefined; -} - -const collectionEntityManagementSchema = Yup.object().shape< - ObjectSchemaDefinition ->({ - entities: Yup.array().of(Yup.object()), -}); +import { CollectionDetailsForm } from '../CollectionDetails/CollectionDetailsForm'; export const CollectionEntityManagement: React.FC = () => { const collectionId = Number( @@ -39,99 +9,5 @@ export const CollectionEntityManagement: React.FC = () => { }>().collectionId, ); - const { loading, data, error } = useCollectionRelatedEntities(collectionId); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const collectionEntityAssignmentMutations = - generateArrayMutationsWithUpdates({ - current: formData.entities, - original: initialData.data?.entities, - generateCreateMutation: (item) => { - const entityTypeId = (() => { - switch (item.entityType) { - case EntityType.Movie: - return 'movieId'; - case EntityType.Tvshow: - return 'tvshowId'; - case EntityType.Season: - return 'seasonId'; - case EntityType.Episode: - return 'episodeId'; - default: { - throw new Error( - `Unsupported entityType found when calling generateCreateMutation.`, - ); - } - } - })(); - - return generateUpdateGQLFragment( - 'createCollectionRelation', - { - input: { - collectionRelation: { - collectionId, - sortOrder: item.sortOrder, - [entityTypeId]: item.entityId, - }, - }, - }, - ); - }, - generateDeleteMutation: (item) => - generateUpdateGQLFragment( - 'deleteCollectionRelation', - { input: { id: item.id as number } }, - ), - generateUpdateMutation: (item) => - generateUpdateGQLFragment( - 'updateCollectionRelation', - { - input: { - id: item.id as number, - patch: { sortOrder: item.sortOrder }, - }, - }, - ), - key: 'id', - }); - - const GqlMutationDocument = gql`mutation UpdateCollectionEntityAssignments { - ${collectionEntityAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [collectionId], - ); - - return ( - - defaultTitle="Entity Management" - validationSchema={collectionEntityManagementSchema} - initialData={{ - data: { entities: data }, - loading, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -const Form: React.FC = () => { - return ( - <> - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementForm.tsx b/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementForm.tsx new file mode 100644 index 00000000..f07761f3 --- /dev/null +++ b/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementForm.tsx @@ -0,0 +1,136 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutationsWithUpdates, + ObjectSchemaDefinition, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { + EntityType, + Mutation, + MutationCreateCollectionRelationArgs, + MutationDeleteCollectionRelationArgs, + MutationUpdateCollectionRelationArgs, +} from '../../../generated/graphql'; +import { CollectionRelatedEntity } from './CollectionEntityManagement.types'; +import { useCollectionRelatedEntities } from './CollectionEntityRelationMapper/CollectionEntityRelationMapper'; +import { EntitySelectField } from './EntitySelectField/EntitySelectField'; + +interface CollectionEntityManagementFormProps { + collectionId: number; +} + +interface FormData { + entities: CollectionRelatedEntity[] | undefined; +} + +const collectionEntityManagementSchema = Yup.object().shape< + ObjectSchemaDefinition +>({ + entities: Yup.array().of(Yup.object()), +}); + +export const CollectionEntityManagementForm: React.FC< + CollectionEntityManagementFormProps +> = ({ collectionId }) => { + const { loading, data, error } = useCollectionRelatedEntities(collectionId); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const collectionEntityAssignmentMutations = + generateArrayMutationsWithUpdates({ + current: formData.entities, + original: initialData.data?.entities, + generateCreateMutation: (item) => { + const entityTypeId = (() => { + switch (item.entityType) { + case EntityType.Movie: + return 'movieId'; + case EntityType.Tvshow: + return 'tvshowId'; + case EntityType.Season: + return 'seasonId'; + case EntityType.Episode: + return 'episodeId'; + default: { + throw new Error( + `Unsupported entityType found when calling generateCreateMutation.`, + ); + } + } + })(); + + return generateUpdateGQLFragment( + 'createCollectionRelation', + { + input: { + collectionRelation: { + collectionId, + sortOrder: item.sortOrder, + [entityTypeId]: item.entityId, + }, + }, + }, + ); + }, + generateDeleteMutation: (item) => + generateUpdateGQLFragment( + 'deleteCollectionRelation', + { input: { id: item.id as number } }, + ), + generateUpdateMutation: (item) => + generateUpdateGQLFragment( + 'updateCollectionRelation', + { + input: { + id: item.id as number, + patch: { sortOrder: item.sortOrder }, + }, + }, + ), + key: 'id', + }); + + const GqlMutationDocument = gql`mutation UpdateCollectionEntityAssignments { + ${collectionEntityAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [collectionId], + ); + + return ( + + defaultTitle="Entity Management" + validationSchema={collectionEntityManagementSchema} + initialData={{ + data: { entities: data }, + loading, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC = () => { + return ( + <> + + + ); +}; diff --git a/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementQuickEdit.tsx new file mode 100644 index 00000000..55eeac82 --- /dev/null +++ b/services/media/workflows/src/Stations/Collections/CollectionEntityManagement/CollectionEntityManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { CollectionData } from '../CollectionsExplorer/Collections.types'; +import { CollectionEntityManagementForm } from './CollectionEntityManagementForm'; + +export const CollectionEntityManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagement.tsx b/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagement.tsx index 43305ad8..853f634f 100644 --- a/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagement.tsx +++ b/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagement.tsx @@ -1,52 +1,6 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - CollectionImageType, - CollectionsImage, - Mutation, - MutationCreateCollectionsImageArgs, - MutationDeleteCollectionsImageByCollectionIdAndImageTypeArgs, - MutationUpdateCollectionsImageByCollectionIdAndImageTypeArgs, - useCollectionImagesQuery, -} from '../../../generated/graphql'; - -type ImageNodes = Pick & { - __typename: 'CollectionsImage'; -}; - -type FormData = Record; - -const Form: React.FC<{ imageSelectField: unknown }> = ({ - imageSelectField, -}) => { - return ( - <> - {Object.keys(CollectionImageType).map((type) => { - const field = CollectionImageType[type]; - return ( - - ); - })} - - ); -}; +import { CollectionImageManagementForm } from './CollectionImageManagementForm'; export const CollectionImageManagement: React.FC = () => { const collectionId = Number( @@ -55,169 +9,5 @@ export const CollectionImageManagement: React.FC = () => { }>().collectionId, ); - const { loading, data, error } = useCollectionImagesQuery({ - client, - variables: { id: collectionId }, - fetchPolicy: 'network-only', - }); - - const { initialImages } = useImageTypes( - data?.collection?.collectionsImages.nodes as ImageNodes[], - ); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const mutations: string[] = []; - - const generateCreateMutation = ( - imageId: string, - imageType: CollectionImageType, - ): string => - generateUpdateGQLFragment( - 'createCollectionsImage', - { - input: { - collectionsImage: { - collectionId, - imageId, - imageType: { type: 'enum', value: imageType }, - }, - }, - }, - ); - - const generateDeleteMutation = (imageType: CollectionImageType): string => - generateUpdateGQLFragment( - 'deleteCollectionsImageByCollectionIdAndImageType', - { - input: { - collectionId, - imageType: { type: 'enum', value: imageType }, - }, - }, - ); - - const generateUpdateMutation = ( - imageId: string, - imageType: CollectionImageType, - ): string => - generateUpdateGQLFragment( - 'updateCollectionsImageByCollectionIdAndImageType', - { - input: { - patch: { imageId }, - collectionId, - imageType: { type: 'enum', value: imageType }, - }, - }, - ); - - Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { - const [imgId] = imageId; - const [initialValue] = initialData?.data?.[imageType]; - const [currentValue] = formData[imageType]; - - switch (true) { - case initialValue === undefined && currentValue !== undefined: - mutations.push( - `assign${idx}:${generateCreateMutation( - imgId, - imageType as CollectionImageType, - )}`, - ); - break; - case initialValue !== undefined && currentValue === undefined: - mutations.push( - `assign${idx}:${generateDeleteMutation( - imageType as CollectionImageType, - )}`, - ); - break; - case initialValue !== currentValue: - mutations.push( - `assign${idx}:${generateUpdateMutation( - currentValue, - imageType as CollectionImageType, - )}`, - ); - break; - default: - break; - } - }); - - const GqlDoc = gql`mutation ImageAssignments { - ${mutations} - }`; - - await client.mutate({ mutation: GqlDoc }); - }, - [collectionId], - ); - - const { ImageSelectField } = useContext(ExtensionsContext); - - return ( - - defaultTitle="Image Management" - initialData={{ - data: initialImages ?? {}, - loading, - entityNotFound: data?.collection === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -/** - * Creates the initial image type values - * @param nodes data nodes - */ -const useImageTypes = ( - nodes: ImageNodes[] = [], -): { - readonly initialImages: FormData; -} => { - const [initialImages, setInitialImages] = useState(getImageTypes()); - - // set all currently assigned images on the server - useEffect(() => { - if (nodes.length > 0) { - let temp = {} as FormData; - - for (const { imageType, imageId } of nodes) { - temp = { ...temp, [imageType]: [imageId] }; - } - - setInitialImages((prevState) => { - return { ...prevState, ...temp }; - }); - } - }, [nodes]); - - return { initialImages } as const; -}; - -/** - * Returns an image type with an empty array(value) using the CollectionImageType enum - */ -const getImageTypes = (): FormData => { - let types = {} as FormData; - - Object.keys(CollectionImageType).map((type) => { - const field = CollectionImageType[type]; - types = { ...types, [field]: [] }; - }); - - return types; + return ; }; diff --git a/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementForm.tsx b/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementForm.tsx new file mode 100644 index 00000000..d57a64f8 --- /dev/null +++ b/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementForm.tsx @@ -0,0 +1,222 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + CollectionImageType, + CollectionsImage, + Mutation, + MutationCreateCollectionsImageArgs, + MutationDeleteCollectionsImageByCollectionIdAndImageTypeArgs, + MutationUpdateCollectionsImageByCollectionIdAndImageTypeArgs, + useCollectionImagesQuery, +} from '../../../generated/graphql'; + +interface CollectionImageManagementFormProps { + collectionId: number; +} + +type ImageNodes = Pick & { + __typename: 'CollectionsImage'; +}; + +type FormData = Record; + +const Form: React.FC<{ imageSelectField: unknown }> = ({ + imageSelectField, +}) => { + return ( + <> + {Object.keys(CollectionImageType).map((type) => { + const field = CollectionImageType[type]; + return ( + + ); + })} + + ); +}; + +export const CollectionImageManagementForm: React.FC< + CollectionImageManagementFormProps +> = ({ collectionId }) => { + const { loading, data, error } = useCollectionImagesQuery({ + client, + variables: { id: collectionId }, + fetchPolicy: 'network-only', + }); + + const { initialImages } = useImageTypes( + data?.collection?.collectionsImages.nodes as ImageNodes[], + ); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const mutations: string[] = []; + + const generateCreateMutation = ( + imageId: string, + imageType: CollectionImageType, + ): string => + generateUpdateGQLFragment( + 'createCollectionsImage', + { + input: { + collectionsImage: { + collectionId, + imageId, + imageType: { type: 'enum', value: imageType }, + }, + }, + }, + ); + + const generateDeleteMutation = (imageType: CollectionImageType): string => + generateUpdateGQLFragment( + 'deleteCollectionsImageByCollectionIdAndImageType', + { + input: { + collectionId, + imageType: { type: 'enum', value: imageType }, + }, + }, + ); + + const generateUpdateMutation = ( + imageId: string, + imageType: CollectionImageType, + ): string => + generateUpdateGQLFragment( + 'updateCollectionsImageByCollectionIdAndImageType', + { + input: { + patch: { imageId }, + collectionId, + imageType: { type: 'enum', value: imageType }, + }, + }, + ); + + Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { + const [imgId] = imageId; + const [initialValue] = initialData?.data?.[imageType]; + const [currentValue] = formData[imageType]; + + switch (true) { + case initialValue === undefined && currentValue !== undefined: + mutations.push( + `assign${idx}:${generateCreateMutation( + imgId, + imageType as CollectionImageType, + )}`, + ); + break; + case initialValue !== undefined && currentValue === undefined: + mutations.push( + `assign${idx}:${generateDeleteMutation( + imageType as CollectionImageType, + )}`, + ); + break; + case initialValue !== currentValue: + mutations.push( + `assign${idx}:${generateUpdateMutation( + currentValue, + imageType as CollectionImageType, + )}`, + ); + break; + default: + break; + } + }); + + const GqlDoc = gql`mutation ImageAssignments { + ${mutations} + }`; + + await client.mutate({ mutation: GqlDoc }); + }, + [collectionId], + ); + + const { ImageSelectField } = useContext(ExtensionsContext); + + return ( + + defaultTitle="Image Management" + initialData={{ + data: initialImages ?? {}, + loading, + entityNotFound: data?.collection === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +/** + * Creates the initial image type values + * @param nodes data nodes + */ +const useImageTypes = ( + nodes: ImageNodes[] = [], +): { + readonly initialImages: FormData; +} => { + const [initialImages, setInitialImages] = useState(getImageTypes()); + + // set all currently assigned images on the server + useEffect(() => { + if (nodes.length > 0) { + let temp = {} as FormData; + + for (const { imageType, imageId } of nodes) { + temp = { ...temp, [imageType]: [imageId] }; + } + + setInitialImages((prevState) => { + return { ...prevState, ...temp }; + }); + } + }, [nodes]); + + return { initialImages } as const; +}; + +/** + * Returns an image type with an empty array(value) using the CollectionImageType enum + */ +const getImageTypes = (): FormData => { + let types = {} as FormData; + + Object.keys(CollectionImageType).map((type) => { + const field = CollectionImageType[type]; + types = { ...types, [field]: [] }; + }); + + return types; +}; diff --git a/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementQuickEdit.tsx new file mode 100644 index 00000000..72da9b87 --- /dev/null +++ b/services/media/workflows/src/Stations/Collections/CollectionImageManagement/CollectionImageManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { CollectionData } from '../CollectionsExplorer/Collections.types'; +import { CollectionImageManagementForm } from './CollectionImageManagementForm'; + +export const CollectionImageManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Collections/CollectionsExplorer/Collections.tsx b/services/media/workflows/src/Stations/Collections/CollectionsExplorer/Collections.tsx index d9d8c446..86b53fe2 100644 --- a/services/media/workflows/src/Stations/Collections/CollectionsExplorer/Collections.tsx +++ b/services/media/workflows/src/Stations/Collections/CollectionsExplorer/Collections.tsx @@ -27,6 +27,9 @@ import { useUnpublishCollectionMutation, } from '../../../generated/graphql'; import { PublishStatusStateMap } from '../../../Util/PublishStatusStateMap/PublishStatusStateMap'; +import { CollectionDetailsQuickEdit } from '../CollectionDetails/CollectionDetailsQuickEdit'; +import { CollectionEntityManagementQuickEdit } from '../CollectionEntityManagement/CollectionEntityManagementQuickEdit'; +import { CollectionImageManagementQuickEdit } from '../CollectionImageManagement/CollectionImageManagementQuickEdit'; import { useCollectionsActions } from './Collections.actions'; import { useCollectionsFilters } from './Collections.filters'; import { CollectionData } from './Collections.types'; @@ -198,6 +201,22 @@ export const Collections: React.FC = () => { filterOptions={filterOptions} defaultSortOrder={{ column: 'updatedDate', direction: 'desc' }} inlineMenuActions={generateInlineMenuActions} + quickEditRegistrations={[ + { + component: , + label: 'Collection Details', + }, + { + component: , + label: 'Manage Entities', + generateDetailsLink: (item) => `/collections/${item.id}/entities`, + }, + { + component: , + label: 'Manage Images', + generateDetailsLink: (item) => `/collections/${item.id}/images`, + }, + ]} /> ); }; diff --git a/services/media/workflows/src/Stations/Collections/registrations.tsx b/services/media/workflows/src/Stations/Collections/registrations.tsx index a1518a07..3c40f4c4 100644 --- a/services/media/workflows/src/Stations/Collections/registrations.tsx +++ b/services/media/workflows/src/Stations/Collections/registrations.tsx @@ -62,12 +62,20 @@ export function register(app: PiletApi, extensions: Extensions): void { categoryName: 'Curation', }); - app.registerPage('/collections', Collections, { - breadcrumb: () => 'Collections', - permissions: { - 'media-service': ['ADMIN', 'COLLECTIONS_EDIT', 'COLLECTIONS_VIEW'], + app.registerPage( + '/collections', + () => ( + + + + ), + { + breadcrumb: () => 'Collections', + permissions: { + 'media-service': ['ADMIN', 'COLLECTIONS_EDIT', 'COLLECTIONS_VIEW'], + }, }, - }); + ); app.registerPage('/collections/create', CollectionCreate, { breadcrumb: () => 'New Collection', diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeExplorerBase/EpisodeExplorer.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeExplorerBase/EpisodeExplorer.tsx index 13ffa00f..a82978aa 100644 --- a/services/media/workflows/src/Stations/Episodes/EpisodeExplorerBase/EpisodeExplorer.tsx +++ b/services/media/workflows/src/Stations/Episodes/EpisodeExplorerBase/EpisodeExplorer.tsx @@ -28,7 +28,6 @@ import { useUnpublishEpisodeMutation, } from '../../../generated/graphql'; import { PublishStatusStateMap } from '../../../Util/PublishStatusStateMap/PublishStatusStateMap'; -import { EpisodeDetailsQuickEdit } from '../EpisodeDetails/EpisodeDetailsQuickEdit'; import { useEpisodesFilters } from './EpisodeExplorer.filters'; import { EpisodeData, EpisodeExplorerProps } from './EpisodeExplorer.types'; import { ExplorerIndexRenderer } from './renderers/ExplorerIndexRenderer'; @@ -218,12 +217,6 @@ export const EpisodeExplorer: React.FC = (props) => { filterOptions={filterOptions} defaultSortOrder={{ column: 'updatedDate', direction: 'desc' }} inlineMenuActions={generateInlineMenuActions} - quickEditRegistrations={[ - { - component: , - label: 'Episode Details', - }, - ]} /> ); case 'SelectionExplorer': diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagement.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagement.tsx index d2fd693d..c24fb1a3 100644 --- a/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagement.tsx +++ b/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagement.tsx @@ -1,52 +1,6 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - EpisodeImageType, - EpisodesImage, - Mutation, - MutationCreateEpisodesImageArgs, - MutationDeleteEpisodesImageByEpisodeIdAndImageTypeArgs, - MutationUpdateEpisodesImageByEpisodeIdAndImageTypeArgs, - useEpisodeImagesQuery, -} from '../../../generated/graphql'; - -type ImageNodes = Pick & { - __typename: 'EpisodesImage'; -}; - -type FormData = Record; - -const Form: React.FC<{ imageSelectField: unknown }> = ({ - imageSelectField, -}) => { - return ( - <> - {Object.keys(EpisodeImageType).map((type) => { - const field = EpisodeImageType[type]; - return ( - - ); - })} - - ); -}; +import { EpisodeImageManagementForm } from './EpisodeImageManagementForm'; export const EpisodeImageManagement: React.FC = () => { const episodeId = Number( @@ -55,166 +9,5 @@ export const EpisodeImageManagement: React.FC = () => { }>().episodeId, ); - const { loading, data, error } = useEpisodeImagesQuery({ - client, - variables: { id: episodeId }, - fetchPolicy: 'network-only', - }); - - const { initialImages } = useImageTypes( - data?.episode?.episodesImages.nodes as ImageNodes[], - ); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const mutations: string[] = []; - - const generateCreateMutation = ( - imageId: string, - imageType: EpisodeImageType, - ): string => - generateUpdateGQLFragment( - 'createEpisodesImage', - { - input: { - episodesImage: { - episodeId, - imageId, - imageType: { type: 'enum', value: imageType }, - }, - }, - }, - ); - - const generateDeleteMutation = (imageType: EpisodeImageType): string => - generateUpdateGQLFragment( - 'deleteEpisodesImageByEpisodeIdAndImageType', - { - input: { episodeId, imageType: { type: 'enum', value: imageType } }, - }, - ); - - const generateUpdateMutation = ( - imageId: string, - imageType: EpisodeImageType, - ): string => - generateUpdateGQLFragment( - 'updateEpisodesImageByEpisodeIdAndImageType', - { - input: { - patch: { imageId }, - episodeId, - imageType: { type: 'enum', value: imageType }, - }, - }, - ); - - Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { - const [imgId] = imageId; - const [initialValue] = initialData?.data?.[imageType]; - const [currentValue] = formData[imageType]; - - switch (true) { - case initialValue === undefined && currentValue !== undefined: - mutations.push( - `assign${idx}:${generateCreateMutation( - imgId, - imageType as EpisodeImageType, - )}`, - ); - break; - case initialValue !== undefined && currentValue === undefined: - mutations.push( - `assign${idx}:${generateDeleteMutation( - imageType as EpisodeImageType, - )}`, - ); - break; - case initialValue !== currentValue: - mutations.push( - `assign${idx}:${generateUpdateMutation( - currentValue, - imageType as EpisodeImageType, - )}`, - ); - break; - default: - break; - } - }); - - const GqlDoc = gql`mutation ImageAssignments { - ${mutations} - }`; - - await client.mutate({ mutation: GqlDoc }); - }, - [episodeId], - ); - - const { ImageSelectField } = useContext(ExtensionsContext); - - return ( - - defaultTitle="Image Management" - initialData={{ - data: initialImages ?? {}, - loading, - entityNotFound: data?.episode === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -/** - * Creates the initial image type values - * @param nodes data nodes - */ -const useImageTypes = ( - nodes: ImageNodes[] = [], -): { - readonly initialImages: FormData; -} => { - const [initialImages, setInitialImages] = useState(getImageTypes()); - - // set all currently assigned images on the server - useEffect(() => { - if (nodes.length > 0) { - let temp = {} as FormData; - - for (const { imageType, imageId } of nodes) { - temp = { ...temp, [imageType]: [imageId] }; - } - - setInitialImages((prevState) => { - return { ...prevState, ...temp }; - }); - } - }, [nodes]); - - return { initialImages } as const; -}; - -/** - * Returns an image type with an empty array(value) using the EpisodeImageType enum - */ -const getImageTypes = (): FormData => { - let types = {} as FormData; - - Object.keys(EpisodeImageType).map((type) => { - const field = EpisodeImageType[type]; - types = { ...types, [field]: [] }; - }); - - return types; + return ; }; diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementForm.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementForm.tsx new file mode 100644 index 00000000..8d21b68c --- /dev/null +++ b/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementForm.tsx @@ -0,0 +1,219 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + EpisodeImageType, + EpisodesImage, + Mutation, + MutationCreateEpisodesImageArgs, + MutationDeleteEpisodesImageByEpisodeIdAndImageTypeArgs, + MutationUpdateEpisodesImageByEpisodeIdAndImageTypeArgs, + useEpisodeImagesQuery, +} from '../../../generated/graphql'; + +interface EpisodeImageManagementFormProps { + episodeId: number; +} + +type ImageNodes = Pick & { + __typename: 'EpisodesImage'; +}; + +type FormData = Record; + +const Form: React.FC<{ imageSelectField: unknown }> = ({ + imageSelectField, +}) => { + return ( + <> + {Object.keys(EpisodeImageType).map((type) => { + const field = EpisodeImageType[type]; + return ( + + ); + })} + + ); +}; + +export const EpisodeImageManagementForm: React.FC< + EpisodeImageManagementFormProps +> = ({ episodeId }) => { + const { loading, data, error } = useEpisodeImagesQuery({ + client, + variables: { id: episodeId }, + fetchPolicy: 'network-only', + }); + + const { initialImages } = useImageTypes( + data?.episode?.episodesImages.nodes as ImageNodes[], + ); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const mutations: string[] = []; + + const generateCreateMutation = ( + imageId: string, + imageType: EpisodeImageType, + ): string => + generateUpdateGQLFragment( + 'createEpisodesImage', + { + input: { + episodesImage: { + episodeId, + imageId, + imageType: { type: 'enum', value: imageType }, + }, + }, + }, + ); + + const generateDeleteMutation = (imageType: EpisodeImageType): string => + generateUpdateGQLFragment( + 'deleteEpisodesImageByEpisodeIdAndImageType', + { + input: { episodeId, imageType: { type: 'enum', value: imageType } }, + }, + ); + + const generateUpdateMutation = ( + imageId: string, + imageType: EpisodeImageType, + ): string => + generateUpdateGQLFragment( + 'updateEpisodesImageByEpisodeIdAndImageType', + { + input: { + patch: { imageId }, + episodeId, + imageType: { type: 'enum', value: imageType }, + }, + }, + ); + + Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { + const [imgId] = imageId; + const [initialValue] = initialData?.data?.[imageType]; + const [currentValue] = formData[imageType]; + + switch (true) { + case initialValue === undefined && currentValue !== undefined: + mutations.push( + `assign${idx}:${generateCreateMutation( + imgId, + imageType as EpisodeImageType, + )}`, + ); + break; + case initialValue !== undefined && currentValue === undefined: + mutations.push( + `assign${idx}:${generateDeleteMutation( + imageType as EpisodeImageType, + )}`, + ); + break; + case initialValue !== currentValue: + mutations.push( + `assign${idx}:${generateUpdateMutation( + currentValue, + imageType as EpisodeImageType, + )}`, + ); + break; + default: + break; + } + }); + + const GqlDoc = gql`mutation ImageAssignments { + ${mutations} + }`; + + await client.mutate({ mutation: GqlDoc }); + }, + [episodeId], + ); + + const { ImageSelectField } = useContext(ExtensionsContext); + + return ( + + defaultTitle="Image Management" + initialData={{ + data: initialImages ?? {}, + loading, + entityNotFound: data?.episode === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +/** + * Creates the initial image type values + * @param nodes data nodes + */ +const useImageTypes = ( + nodes: ImageNodes[] = [], +): { + readonly initialImages: FormData; +} => { + const [initialImages, setInitialImages] = useState(getImageTypes()); + + // set all currently assigned images on the server + useEffect(() => { + if (nodes.length > 0) { + let temp = {} as FormData; + + for (const { imageType, imageId } of nodes) { + temp = { ...temp, [imageType]: [imageId] }; + } + + setInitialImages((prevState) => { + return { ...prevState, ...temp }; + }); + } + }, [nodes]); + + return { initialImages } as const; +}; + +/** + * Returns an image type with an empty array(value) using the EpisodeImageType enum + */ +const getImageTypes = (): FormData => { + let types = {} as FormData; + + Object.keys(EpisodeImageType).map((type) => { + const field = EpisodeImageType[type]; + types = { ...types, [field]: [] }; + }); + + return types; +}; diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementQuickEdit.tsx new file mode 100644 index 00000000..24c0b695 --- /dev/null +++ b/services/media/workflows/src/Stations/Episodes/EpisodeImageManagement/EpisodeImageManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { EpisodeData } from '../EpisodeExplorerBase/EpisodeExplorer.types'; +import { EpisodeImageManagementForm } from './EpisodeImageManagementForm'; + +export const EpisodeImageManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagement.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagement.tsx index dd5858c0..035ed41e 100644 --- a/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagement.tsx +++ b/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagement.tsx @@ -1,37 +1,6 @@ -import { ID } from '@axinom/mosaic-managed-workflow-integration'; -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutations, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; -import React, { useCallback, useContext } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - Mutation, - MutationCreateEpisodesTrailerArgs, - MutationDeleteEpisodesTrailerArgs, - MutationUpdateEpisodeArgs, - useEpisodeVideosQuery, -} from '../../../generated/graphql'; - -interface FormData { - mainVideo: ID[]; - trailerVideos: ID[]; -} - -const episodeVideoManagementSchema = Yup.object().shape< - ObjectSchemaDefinition ->({ - mainVideo: Yup.array().of(Yup.mixed()).max(1), - trailerVideos: Yup.array().of(Yup.mixed()), -}); +import { EpisodeVideoManagementForm } from './EpisodeVideoManagementForm'; export const EpisodeVideoManagement: React.FC = () => { const episodeId = Number( @@ -40,106 +9,5 @@ export const EpisodeVideoManagement: React.FC = () => { }>().episodeId, ); - const { VideoSelectField } = useContext(ExtensionsContext); - - const { loading, data, error } = useEpisodeVideosQuery({ - client, - variables: { id: episodeId }, - fetchPolicy: 'network-only', - }); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const trailerAssignmentMutations = generateArrayMutations({ - current: formData.trailerVideos, - original: initialData.data?.trailerVideos, - generateCreateMutation: (videoId) => - generateUpdateGQLFragment( - 'createEpisodesTrailer', - { input: { episodesTrailer: { videoId, episodeId } } }, - ), - generateDeleteMutation: (videoId) => - generateUpdateGQLFragment( - 'deleteEpisodesTrailer', - { input: { episodeId, videoId } }, - ), - prefix: 'episodeTrailers', - }); - - const mainVideoUpdateMutation = - initialData.data?.mainVideo[0] !== formData.mainVideo[0] - ? generateUpdateGQLFragment( - 'updateEpisode', - { - input: { - id: episodeId, - patch: { mainVideoId: formData.mainVideo[0] ?? null }, - }, - }, - ) - : ''; - - const GqlMutationDocument = gql`mutation UpdateEpisodeVideos { - ${mainVideoUpdateMutation} - ${trailerAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [episodeId], - ); - - return ( - - defaultTitle="Video Management" - validationSchema={episodeVideoManagementSchema} - initialData={{ - data: { - mainVideo: data?.episode?.mainVideoId - ? [data?.episode?.mainVideoId] - : [], - trailerVideos: - data?.episode?.episodesTrailers.nodes.map( - (trailer) => trailer.videoId, - ) ?? [], - }, - loading, - entityNotFound: data?.episode === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -const Form: React.FC<{ videoSelectField: unknown }> = ({ - videoSelectField, -}) => { - return ( - <> - - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementForm.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementForm.tsx new file mode 100644 index 00000000..529d9be9 --- /dev/null +++ b/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementForm.tsx @@ -0,0 +1,144 @@ +import { ID } from '@axinom/mosaic-managed-workflow-integration'; +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutations, + ObjectSchemaDefinition, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + Mutation, + MutationCreateEpisodesTrailerArgs, + MutationDeleteEpisodesTrailerArgs, + MutationUpdateEpisodeArgs, + useEpisodeVideosQuery, +} from '../../../generated/graphql'; + +interface FormData { + mainVideo: ID[]; + trailerVideos: ID[]; +} + +interface EpisodeVideoManagementFormProps { + episodeId: number; +} + +const episodeVideoManagementSchema = Yup.object().shape< + ObjectSchemaDefinition +>({ + mainVideo: Yup.array().of(Yup.mixed()).max(1), + trailerVideos: Yup.array().of(Yup.mixed()), +}); + +export const EpisodeVideoManagementForm: React.FC< + EpisodeVideoManagementFormProps +> = ({ episodeId }) => { + const { VideoSelectField } = useContext(ExtensionsContext); + + const { loading, data, error } = useEpisodeVideosQuery({ + client, + variables: { id: episodeId }, + fetchPolicy: 'network-only', + }); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const trailerAssignmentMutations = generateArrayMutations({ + current: formData.trailerVideos, + original: initialData.data?.trailerVideos, + generateCreateMutation: (videoId) => + generateUpdateGQLFragment( + 'createEpisodesTrailer', + { input: { episodesTrailer: { videoId, episodeId } } }, + ), + generateDeleteMutation: (videoId) => + generateUpdateGQLFragment( + 'deleteEpisodesTrailer', + { input: { episodeId, videoId } }, + ), + prefix: 'episodeTrailers', + }); + + const mainVideoUpdateMutation = + initialData.data?.mainVideo[0] !== formData.mainVideo[0] + ? generateUpdateGQLFragment( + 'updateEpisode', + { + input: { + id: episodeId, + patch: { mainVideoId: formData.mainVideo[0] ?? null }, + }, + }, + ) + : ''; + + const GqlMutationDocument = gql`mutation UpdateEpisodeVideos { + ${mainVideoUpdateMutation} + ${trailerAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [episodeId], + ); + + return ( + + defaultTitle="Video Management" + validationSchema={episodeVideoManagementSchema} + initialData={{ + data: { + mainVideo: data?.episode?.mainVideoId + ? [data?.episode?.mainVideoId] + : [], + trailerVideos: + data?.episode?.episodesTrailers.nodes.map( + (trailer) => trailer.videoId, + ) ?? [], + }, + loading, + entityNotFound: data?.episode === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC<{ videoSelectField: unknown }> = ({ + videoSelectField, +}) => { + return ( + <> + + + + ); +}; diff --git a/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementQuickEdit.tsx new file mode 100644 index 00000000..a0443ca2 --- /dev/null +++ b/services/media/workflows/src/Stations/Episodes/EpisodeVideoManagement/EpisodeVideoManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { EpisodeData } from '../EpisodeExplorerBase/EpisodeExplorer.types'; +import { EpisodeVideoManagementForm } from './EpisodeVideoManagementForm'; + +export const EpisodeVideoManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Episodes/EpisodesExplorer/Episodes.tsx b/services/media/workflows/src/Stations/Episodes/EpisodesExplorer/Episodes.tsx index dc61d12c..dc6ae1c8 100644 --- a/services/media/workflows/src/Stations/Episodes/EpisodesExplorer/Episodes.tsx +++ b/services/media/workflows/src/Stations/Episodes/EpisodesExplorer/Episodes.tsx @@ -1,5 +1,8 @@ import React from 'react'; +import { EpisodeDetailsQuickEdit } from '../EpisodeDetails/EpisodeDetailsQuickEdit'; import { EpisodeExplorer } from '../EpisodeExplorerBase/EpisodeExplorer'; +import { EpisodeImageManagementQuickEdit } from '../EpisodeImageManagement/EpisodeImageManagementQuickEdit'; +import { EpisodeVideoManagementQuickEdit } from '../EpisodeVideoManagement/EpisodeVideoManagementQuickEdit'; import { useEpisodesActions } from './Episodes.actions'; export const Episodes: React.FC = () => { @@ -13,6 +16,22 @@ export const Episodes: React.FC = () => { bulkActions={bulkActions} calculateNavigateUrl={(item) => `/episodes/${item.id}`} onCreateAction="/episodes/create" + quickEditRegistrations={[ + { + component: , + label: 'Episode Details', + }, + { + component: , + label: 'Manage Videos', + generateDetailsLink: (item) => `/episodes/${item.id}/videos`, + }, + { + component: , + label: 'Manage Images', + generateDetailsLink: (item) => `/episodes/${item.id}/images`, + }, + ]} /> ); }; diff --git a/services/media/workflows/src/Stations/Episodes/registrations.tsx b/services/media/workflows/src/Stations/Episodes/registrations.tsx index f5c45f2e..777be585 100644 --- a/services/media/workflows/src/Stations/Episodes/registrations.tsx +++ b/services/media/workflows/src/Stations/Episodes/registrations.tsx @@ -64,10 +64,20 @@ export function register(app: PiletApi, extensions: Extensions): void { categoryName: 'Content', }); - app.registerPage('/episodes', Episodes, { - breadcrumb: () => 'Episodes', - permissions: { 'media-service': ['ADMIN', 'TVSHOWS_EDIT', 'TVSHOWS_VIEW'] }, - }); + app.registerPage( + '/episodes', + () => ( + + + + ), + { + breadcrumb: () => 'Episodes', + permissions: { + 'media-service': ['ADMIN', 'TVSHOWS_EDIT', 'TVSHOWS_VIEW'], + }, + }, + ); app.registerPage('/episodes/create', EpisodeCreate, { breadcrumb: () => 'New Episode', diff --git a/services/media/workflows/src/Stations/Movies/MovieExplorerBase/MovieExplorer.tsx b/services/media/workflows/src/Stations/Movies/MovieExplorerBase/MovieExplorer.tsx index 943f8966..8ac9f0d3 100644 --- a/services/media/workflows/src/Stations/Movies/MovieExplorerBase/MovieExplorer.tsx +++ b/services/media/workflows/src/Stations/Movies/MovieExplorerBase/MovieExplorer.tsx @@ -29,7 +29,6 @@ import { useUnpublishMovieMutation, } from '../../../generated/graphql'; import { PublishStatusStateMap } from '../../../Util/PublishStatusStateMap/PublishStatusStateMap'; -import { MovieDetailsQuickEdit } from '../MovieDetails/MovieDetailsQuickEdit'; import { useMoviesFilters } from './MovieExplorer.filters'; import { MovieData, MovieExplorerProps } from './MovieExplorer.types'; @@ -204,9 +203,6 @@ export const MovieExplorer: React.FC = (props) => { filterOptions={filterOptions} defaultSortOrder={{ column: 'updatedDate', direction: 'desc' }} inlineMenuActions={generateInlineMenuActions} - quickEditRegistrations={[ - { component: , label: 'Movie Details' }, - ]} /> ); case 'SelectionExplorer': diff --git a/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagement.tsx b/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagement.tsx index 3a5c7101..302934c2 100644 --- a/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagement.tsx +++ b/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagement.tsx @@ -1,52 +1,6 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - MovieImageType, - MoviesImage, - Mutation, - MutationCreateMoviesImageArgs, - MutationDeleteMoviesImageByMovieIdAndImageTypeArgs, - MutationUpdateMoviesImageByMovieIdAndImageTypeArgs, - useMovieImagesQuery, -} from '../../../generated/graphql'; - -type ImageNodes = Pick & { - __typename: 'MoviesImage'; -}; - -type FormData = Record; - -const Form: React.FC<{ imageSelectField: unknown }> = ({ - imageSelectField, -}) => { - return ( - <> - {Object.keys(MovieImageType).map((type) => { - const field = MovieImageType[type]; - return ( - - ); - })} - - ); -}; +import { MovieImageManagementForm } from './MovieImageManagementForm'; export const MovieImageManagement: React.FC = () => { const movieId = Number( @@ -54,167 +8,5 @@ export const MovieImageManagement: React.FC = () => { movieId: string; }>().movieId, ); - - const { loading, data, error } = useMovieImagesQuery({ - client, - variables: { id: movieId }, - fetchPolicy: 'network-only', - }); - - const { initialImages } = useImageTypes( - data?.movie?.moviesImages.nodes as ImageNodes[], - ); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const mutations: string[] = []; - - const generateCreateMutation = ( - imageId: string, - imageType: MovieImageType, - ): string => - generateUpdateGQLFragment( - 'createMoviesImage', - { - input: { - moviesImage: { - movieId, - imageId, - imageType: { type: 'enum', value: imageType }, - }, - }, - }, - ); - - const generateDeleteMutation = (imageType: MovieImageType): string => - generateUpdateGQLFragment( - 'deleteMoviesImageByMovieIdAndImageType', - { - input: { movieId, imageType: { type: 'enum', value: imageType } }, - }, - ); - - const generateUpdateMutation = ( - imageId: string, - imageType: MovieImageType, - ): string => - generateUpdateGQLFragment( - 'updateMoviesImageByMovieIdAndImageType', - { - input: { - patch: { imageId }, - movieId, - imageType: { type: 'enum', value: imageType }, - }, - }, - ); - - Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { - const [imgId] = imageId; - const [initialValue] = initialData?.data?.[imageType]; - const [currentValue] = formData[imageType]; - - switch (true) { - case initialValue === undefined && currentValue !== undefined: - mutations.push( - `assign${idx}:${generateCreateMutation( - imgId, - imageType as MovieImageType, - )}`, - ); - break; - case initialValue !== undefined && currentValue === undefined: - mutations.push( - `assign${idx}:${generateDeleteMutation( - imageType as MovieImageType, - )}`, - ); - break; - case initialValue !== currentValue: - mutations.push( - `assign${idx}:${generateUpdateMutation( - currentValue, - imageType as MovieImageType, - )}`, - ); - break; - default: - break; - } - }); - - const GqlDoc = gql`mutation ImageAssignments { - ${mutations} - }`; - - await client.mutate({ mutation: GqlDoc }); - }, - [movieId], - ); - - const { ImageSelectField } = useContext(ExtensionsContext); - - return ( - - defaultTitle="Image Management" - initialData={{ - data: initialImages ?? {}, - loading, - entityNotFound: data?.movie === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -/** - * Creates the initial image type values - * @param nodes data nodes - */ -const useImageTypes = ( - nodes: ImageNodes[] = [], -): { - readonly initialImages: FormData; -} => { - const [initialImages, setInitialImages] = useState(getImageTypes()); - - // set all currently assigned images on the server - useEffect(() => { - if (nodes.length > 0) { - let temp = {} as FormData; - - for (const { imageType, imageId } of nodes) { - temp = { ...temp, [imageType]: [imageId] }; - } - - setInitialImages((prevState) => { - return { ...prevState, ...temp }; - }); - } - }, [nodes]); - - return { initialImages } as const; -}; - -/** - * Returns an image type with an empty array(value) using the MovieImageType enum - */ -const getImageTypes = (): FormData => { - let types = {} as FormData; - - Object.keys(MovieImageType).map((type) => { - const field = MovieImageType[type]; - types = { ...types, [field]: [] }; - }); - - return types; + return ; }; diff --git a/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementForm.tsx b/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementForm.tsx new file mode 100644 index 00000000..4048ab4a --- /dev/null +++ b/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementForm.tsx @@ -0,0 +1,219 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + MovieImageType, + MoviesImage, + Mutation, + MutationCreateMoviesImageArgs, + MutationDeleteMoviesImageByMovieIdAndImageTypeArgs, + MutationUpdateMoviesImageByMovieIdAndImageTypeArgs, + useMovieImagesQuery, +} from '../../../generated/graphql'; + +interface MovieImageManagementFormProps { + movieId: number; +} + +type ImageNodes = Pick & { + __typename: 'MoviesImage'; +}; + +type FormData = Record; + +const Form: React.FC<{ imageSelectField: unknown }> = ({ + imageSelectField, +}) => { + return ( + <> + {Object.keys(MovieImageType).map((type) => { + const field = MovieImageType[type]; + return ( + + ); + })} + + ); +}; + +export const MovieImageManagementForm: React.FC< + MovieImageManagementFormProps +> = ({ movieId }) => { + const { loading, data, error } = useMovieImagesQuery({ + client, + variables: { id: movieId }, + fetchPolicy: 'network-only', + }); + + const { initialImages } = useImageTypes( + data?.movie?.moviesImages.nodes as ImageNodes[], + ); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const mutations: string[] = []; + + const generateCreateMutation = ( + imageId: string, + imageType: MovieImageType, + ): string => + generateUpdateGQLFragment( + 'createMoviesImage', + { + input: { + moviesImage: { + movieId, + imageId, + imageType: { type: 'enum', value: imageType }, + }, + }, + }, + ); + + const generateDeleteMutation = (imageType: MovieImageType): string => + generateUpdateGQLFragment( + 'deleteMoviesImageByMovieIdAndImageType', + { + input: { movieId, imageType: { type: 'enum', value: imageType } }, + }, + ); + + const generateUpdateMutation = ( + imageId: string, + imageType: MovieImageType, + ): string => + generateUpdateGQLFragment( + 'updateMoviesImageByMovieIdAndImageType', + { + input: { + patch: { imageId }, + movieId, + imageType: { type: 'enum', value: imageType }, + }, + }, + ); + + Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { + const [imgId] = imageId; + const [initialValue] = initialData?.data?.[imageType]; + const [currentValue] = formData[imageType]; + + switch (true) { + case initialValue === undefined && currentValue !== undefined: + mutations.push( + `assign${idx}:${generateCreateMutation( + imgId, + imageType as MovieImageType, + )}`, + ); + break; + case initialValue !== undefined && currentValue === undefined: + mutations.push( + `assign${idx}:${generateDeleteMutation( + imageType as MovieImageType, + )}`, + ); + break; + case initialValue !== currentValue: + mutations.push( + `assign${idx}:${generateUpdateMutation( + currentValue, + imageType as MovieImageType, + )}`, + ); + break; + default: + break; + } + }); + + const GqlDoc = gql`mutation ImageAssignments { + ${mutations} + }`; + + await client.mutate({ mutation: GqlDoc }); + }, + [movieId], + ); + + const { ImageSelectField } = useContext(ExtensionsContext); + + return ( + + defaultTitle="Image Management" + initialData={{ + data: initialImages ?? {}, + loading, + entityNotFound: data?.movie === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +/** + * Creates the initial image type values + * @param nodes data nodes + */ +const useImageTypes = ( + nodes: ImageNodes[] = [], +): { + readonly initialImages: FormData; +} => { + const [initialImages, setInitialImages] = useState(getImageTypes()); + + // set all currently assigned images on the server + useEffect(() => { + if (nodes.length > 0) { + let temp = {} as FormData; + + for (const { imageType, imageId } of nodes) { + temp = { ...temp, [imageType]: [imageId] }; + } + + setInitialImages((prevState) => { + return { ...prevState, ...temp }; + }); + } + }, [nodes]); + + return { initialImages } as const; +}; + +/** + * Returns an image type with an empty array(value) using the MovieImageType enum + */ +const getImageTypes = (): FormData => { + let types = {} as FormData; + + Object.keys(MovieImageType).map((type) => { + const field = MovieImageType[type]; + types = { ...types, [field]: [] }; + }); + + return types; +}; diff --git a/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementQuickEdit.tsx new file mode 100644 index 00000000..cc4cdcce --- /dev/null +++ b/services/media/workflows/src/Stations/Movies/MovieImageManagement/MovieImageManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { MovieData } from '../MovieExplorerBase/MovieExplorer.types'; +import { MovieImageManagementForm } from './MovieImageManagementForm'; + +export const MovieImageManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagement.tsx b/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagement.tsx index 482e93dc..7127989d 100644 --- a/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagement.tsx +++ b/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagement.tsx @@ -1,140 +1,13 @@ -import { ID } from '@axinom/mosaic-managed-workflow-integration'; -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutations, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; -import React, { useCallback, useContext } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - Mutation, - MutationCreateMoviesTrailerArgs, - MutationDeleteMoviesTrailerArgs, - MutationUpdateMovieArgs, - useMovieVideosQuery, -} from '../../../generated/graphql'; - -interface FormData { - mainVideo: ID[]; - trailerVideos: ID[]; -} - -const movieVideoManagementSchema = Yup.object().shape< - ObjectSchemaDefinition ->({ - mainVideo: Yup.array().of(Yup.mixed()).max(1), - trailerVideos: Yup.array().of(Yup.mixed()), -}); +import { MovieVideoManagementForm } from './MovieVideoManagementForm'; export const MovieVideoManagement: React.FC = () => { - const { VideoSelectField } = useContext(ExtensionsContext); - const movieId = Number( useParams<{ movieId: string; }>().movieId, ); - const { loading, data, error } = useMovieVideosQuery({ - client, - variables: { id: movieId }, - fetchPolicy: 'no-cache', - }); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const trailerAssignmentMutations = generateArrayMutations({ - current: formData.trailerVideos, - original: initialData.data?.trailerVideos, - generateCreateMutation: (videoId) => - generateUpdateGQLFragment( - 'createMoviesTrailer', - { input: { moviesTrailer: { videoId, movieId } } }, - ), - generateDeleteMutation: (videoId) => - generateUpdateGQLFragment( - 'deleteMoviesTrailer', - { input: { movieId, videoId } }, - ), - prefix: 'movieTrailers', - }); - - const mainVideoUpdateMutation = - initialData.data?.mainVideo[0] !== formData.mainVideo[0] - ? generateUpdateGQLFragment('updateMovie', { - input: { - id: movieId, - patch: { mainVideoId: formData.mainVideo[0] ?? null }, - }, - }) - : ''; - - const GqlMutationDocument = gql`mutation UpdateMovieVideos { - ${mainVideoUpdateMutation} - ${trailerAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [movieId], - ); - - return ( - - defaultTitle="Video Management" - validationSchema={movieVideoManagementSchema} - initialData={{ - data: { - mainVideo: data?.movie?.mainVideoId ? [data?.movie?.mainVideoId] : [], - trailerVideos: - data?.movie?.moviesTrailers.nodes.map( - (trailer) => trailer.videoId, - ) ?? [], - }, - loading, - entityNotFound: data?.movie === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -const Form: React.FC<{ videoSelectField: unknown }> = ({ - videoSelectField, -}) => { - return ( - <> - - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementForm.tsx b/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementForm.tsx new file mode 100644 index 00000000..cdc72cf6 --- /dev/null +++ b/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementForm.tsx @@ -0,0 +1,139 @@ +import { ID } from '@axinom/mosaic-managed-workflow-integration'; +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutations, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; +import React, { useCallback, useContext } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + Mutation, + MutationCreateMoviesTrailerArgs, + MutationDeleteMoviesTrailerArgs, + MutationUpdateMovieArgs, + useMovieVideosQuery, +} from '../../../generated/graphql'; + +interface FormData { + mainVideo: ID[]; + trailerVideos: ID[]; +} + +interface MovieVideoManagementFormProps { + movieId: number; +} + +const movieVideoManagementSchema = Yup.object().shape< + ObjectSchemaDefinition +>({ + mainVideo: Yup.array().of(Yup.mixed()).max(1), + trailerVideos: Yup.array().of(Yup.mixed()), +}); + +export const MovieVideoManagementForm: React.FC< + MovieVideoManagementFormProps +> = ({ movieId }) => { + const { VideoSelectField } = useContext(ExtensionsContext); + + const { loading, data, error } = useMovieVideosQuery({ + client, + variables: { id: movieId }, + fetchPolicy: 'no-cache', + }); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const trailerAssignmentMutations = generateArrayMutations({ + current: formData.trailerVideos, + original: initialData.data?.trailerVideos, + generateCreateMutation: (videoId) => + generateUpdateGQLFragment( + 'createMoviesTrailer', + { input: { moviesTrailer: { videoId, movieId } } }, + ), + generateDeleteMutation: (videoId) => + generateUpdateGQLFragment( + 'deleteMoviesTrailer', + { input: { movieId, videoId } }, + ), + prefix: 'movieTrailers', + }); + + const mainVideoUpdateMutation = + initialData.data?.mainVideo[0] !== formData.mainVideo[0] + ? generateUpdateGQLFragment('updateMovie', { + input: { + id: movieId, + patch: { mainVideoId: formData.mainVideo[0] ?? null }, + }, + }) + : ''; + + const GqlMutationDocument = gql`mutation UpdateMovieVideos { + ${mainVideoUpdateMutation} + ${trailerAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [movieId], + ); + + return ( + + defaultTitle="Video Management" + validationSchema={movieVideoManagementSchema} + initialData={{ + data: { + mainVideo: data?.movie?.mainVideoId ? [data?.movie?.mainVideoId] : [], + trailerVideos: + data?.movie?.moviesTrailers.nodes.map( + (trailer) => trailer.videoId, + ) ?? [], + }, + loading, + entityNotFound: data?.movie === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC<{ videoSelectField: unknown }> = ({ + videoSelectField, +}) => { + return ( + <> + + + + ); +}; diff --git a/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementQuickEdit.tsx new file mode 100644 index 00000000..e6bab384 --- /dev/null +++ b/services/media/workflows/src/Stations/Movies/MovieVideoManagement/MovieVideoManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { MovieData } from '../MovieExplorerBase/MovieExplorer.types'; +import { MovieVideoManagementForm } from './MovieVideoManagementForm'; + +export const MovieVideoManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Movies/MoviesExplorer/Movies.tsx b/services/media/workflows/src/Stations/Movies/MoviesExplorer/Movies.tsx index 185231fc..1ffb0a9e 100644 --- a/services/media/workflows/src/Stations/Movies/MoviesExplorer/Movies.tsx +++ b/services/media/workflows/src/Stations/Movies/MoviesExplorer/Movies.tsx @@ -1,5 +1,8 @@ import React from 'react'; +import { MovieDetailsQuickEdit } from '../MovieDetails/MovieDetailsQuickEdit'; import { MovieExplorer } from '../MovieExplorerBase/MovieExplorer'; +import { MovieImageManagementQuickEdit } from '../MovieImageManagement/MovieImageManagementQuickEdit'; +import { MovieVideoManagementQuickEdit } from '../MovieVideoManagement/MovieVideoManagementQuickEdit'; import { useMoviesActions } from './Movies.actions'; export const Movies: React.FC = () => { @@ -13,6 +16,19 @@ export const Movies: React.FC = () => { calculateNavigateUrl={(item) => `/movies/${item.id}`} onCreateAction="/movies/create" bulkActions={bulkActions} + quickEditRegistrations={[ + { component: , label: 'Movie Details' }, + { + component: , + label: 'Manage Videos', + generateDetailsLink: (item) => `/movies/${item.id}/videos`, + }, + { + component: , + label: 'Manage Images', + generateDetailsLink: (item) => `/movies/${item.id}/images`, + }, + ]} /> ); }; diff --git a/services/media/workflows/src/Stations/Movies/registrations.tsx b/services/media/workflows/src/Stations/Movies/registrations.tsx index 92aabdfd..839a0e64 100644 --- a/services/media/workflows/src/Stations/Movies/registrations.tsx +++ b/services/media/workflows/src/Stations/Movies/registrations.tsx @@ -81,10 +81,18 @@ export function register(app: PiletApi, extensions: Extensions): void { categoryName: 'Content', }); - app.registerPage('/movies', Movies, { - breadcrumb: () => 'Movies', - permissions: { 'media-service': ['ADMIN', 'MOVIES_EDIT', 'MOVIES_VIEW'] }, - }); + app.registerPage( + '/movies', + () => ( + + + + ), + { + breadcrumb: () => 'Movies', + permissions: { 'media-service': ['ADMIN', 'MOVIES_EDIT', 'MOVIES_VIEW'] }, + }, + ); app.registerPage('/movies/create', MovieCreate, { breadcrumb: () => 'New Movie', permissions: { 'media-service': ['ADMIN', 'MOVIES_EDIT', 'MOVIES_VIEW'] }, diff --git a/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagement.tsx b/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagement.tsx index d254ef17..6bcc6d0b 100644 --- a/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagement.tsx +++ b/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagement.tsx @@ -1,102 +1,11 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutations, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; -import React, { useCallback } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { - Mutation, - MutationUpdateEpisodeArgs, - useSeasonEpisodesQuery, -} from '../../../generated/graphql'; -import { EpisodeSelectField } from './EpisodeSelectField/EpisodeSelectField'; -import { SeasonEpisode } from './SeasonEpisodeManagement.types'; - -interface FormData { - episodes: SeasonEpisode[]; -} - -const seasonEpisodeManagementSchema = Yup.object().shape< - ObjectSchemaDefinition ->({ - episodes: Yup.array().of(Yup.object()), -}); +import { SeasonEpisodeManagementForm } from './SeasonEpisodeManagementForm'; export const SeasonEpisodeManagement: React.FC = () => { - const seasonId = Number( - useParams<{ - seasonId: string; - }>().seasonId, - ); - - const { loading, data, error } = useSeasonEpisodesQuery({ - client, - variables: { id: seasonId }, - fetchPolicy: 'no-cache', - }); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const episodeAssignmentMutations = generateArrayMutations({ - current: formData.episodes, - original: initialData.data?.episodes, - generateCreateMutation: ({ id }) => - generateUpdateGQLFragment( - 'updateEpisode', - { input: { id, patch: { seasonId: seasonId } } }, - ), - generateDeleteMutation: ({ id }) => - generateUpdateGQLFragment( - 'updateEpisode', - { input: { id, patch: { seasonId: null } } }, - ), - }); - - const GqlMutationDocument = gql`mutation UpdateSeasonEpisodes { - ${episodeAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [seasonId], - ); - - return ( - - defaultTitle="Episode Management" - validationSchema={seasonEpisodeManagementSchema} - initialData={{ - data: { - episodes: data?.season?.episodes.nodes ?? [], - }, - loading, - entityNotFound: data?.season === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; + const { seasonId } = useParams<{ + seasonId: string; + }>(); -const Form: React.FC = () => { - return ( - <> - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementForm.tsx b/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementForm.tsx new file mode 100644 index 00000000..7fb4487b --- /dev/null +++ b/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementForm.tsx @@ -0,0 +1,101 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutations, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; +import React, { useCallback } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { + Mutation, + MutationUpdateEpisodeArgs, + useSeasonEpisodesQuery, +} from '../../../generated/graphql'; +import { EpisodeSelectField } from './EpisodeSelectField/EpisodeSelectField'; +import { SeasonEpisode } from './SeasonEpisodeManagement.types'; + +interface SeasonEpisodeManagementFormProps { + seasonId: number; +} + +interface FormData { + episodes: SeasonEpisode[]; +} + +const seasonEpisodeManagementSchema = Yup.object().shape< + ObjectSchemaDefinition +>({ + episodes: Yup.array().of(Yup.object()), +}); + +export const SeasonEpisodeManagementForm: React.FC< + SeasonEpisodeManagementFormProps +> = ({ seasonId }) => { + const { loading, data, error } = useSeasonEpisodesQuery({ + client, + variables: { id: seasonId }, + fetchPolicy: 'no-cache', + }); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const episodeAssignmentMutations = generateArrayMutations({ + current: formData.episodes, + original: initialData.data?.episodes, + generateCreateMutation: ({ id }) => + generateUpdateGQLFragment( + 'updateEpisode', + { input: { id, patch: { seasonId: seasonId } } }, + ), + generateDeleteMutation: ({ id }) => + generateUpdateGQLFragment( + 'updateEpisode', + { input: { id, patch: { seasonId: null } } }, + ), + }); + + const GqlMutationDocument = gql`mutation UpdateSeasonEpisodes { + ${episodeAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [seasonId], + ); + + return ( + + defaultTitle="Episode Management" + validationSchema={seasonEpisodeManagementSchema} + initialData={{ + data: { + episodes: data?.season?.episodes.nodes ?? [], + }, + loading, + entityNotFound: data?.season === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC = () => { + return ( + <> + + + ); +}; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementQuickEdit.tsx new file mode 100644 index 00000000..69742380 --- /dev/null +++ b/services/media/workflows/src/Stations/Seasons/SeasonEpisodeManagement/SeasonEpisodeManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { SeasonData } from '../SeasonExplorerBase/SeasonExplorer.types'; +import { SeasonEpisodeManagementForm } from './SeasonEpisodeManagementForm'; + +export const SeasonEpisodeManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonExplorerBase/SeasonExplorer.tsx b/services/media/workflows/src/Stations/Seasons/SeasonExplorerBase/SeasonExplorer.tsx index 7db8040c..bcaddb23 100644 --- a/services/media/workflows/src/Stations/Seasons/SeasonExplorerBase/SeasonExplorer.tsx +++ b/services/media/workflows/src/Stations/Seasons/SeasonExplorerBase/SeasonExplorer.tsx @@ -28,7 +28,6 @@ import { useUnpublishSeasonMutation, } from '../../../generated/graphql'; import { PublishStatusStateMap } from '../../../Util/PublishStatusStateMap/PublishStatusStateMap'; -import { SeasonDetailsQuickEdit } from '../SeasonDetails/SeasonDetailsQuickEdit'; import { SeasonIndexRenderer } from './renderers/SeasonIndexRenderer'; import { SeasonParentRenderer } from './renderers/SeasonParentRenderer'; import { useSeasonsFilters } from './SeasonExplorer.filters'; @@ -215,9 +214,6 @@ export const SeasonExplorer: React.FC = (props) => { filterOptions={filterOptions} defaultSortOrder={{ column: 'updatedDate', direction: 'desc' }} inlineMenuActions={generateInlineMenuActions} - quickEditRegistrations={[ - { component: , label: 'Season Details' }, - ]} /> ); case 'SelectionExplorer': diff --git a/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagement.tsx b/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagement.tsx index 1e7c9a6b..bd5caf49 100644 --- a/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagement.tsx +++ b/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagement.tsx @@ -1,220 +1,11 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - Mutation, - MutationCreateSeasonsImageArgs, - MutationDeleteSeasonsImageBySeasonIdAndImageTypeArgs, - MutationUpdateSeasonsImageBySeasonIdAndImageTypeArgs, - SeasonImageType, - SeasonsImage, - useSeasonImagesQuery, -} from '../../../generated/graphql'; - -type ImageNodes = Pick & { - __typename: 'SeasonsImage'; -}; - -type FormData = Record; - -const Form: React.FC<{ imageSelectField: unknown }> = ({ - imageSelectField, -}) => { - return ( - <> - {Object.keys(SeasonImageType).map((type) => { - const field = SeasonImageType[type]; - return ( - - ); - })} - - ); -}; +import { SeasonImageManagementForm } from './SeasonImageManagementForm'; export const SeasonImageManagement: React.FC = () => { - const seasonId = Number( - useParams<{ - seasonId: string; - }>().seasonId, - ); - - const { loading, data, error } = useSeasonImagesQuery({ - client, - variables: { id: seasonId }, - fetchPolicy: 'no-cache', - }); - - const { initialImages } = useImageTypes( - data?.season?.seasonsImages.nodes as ImageNodes[], - ); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const mutations: string[] = []; - - const generateCreateMutation = ( - imageId: string, - imageType: SeasonImageType, - ): string => - generateUpdateGQLFragment( - 'createSeasonsImage', - { - input: { - seasonsImage: { - seasonId, - imageId, - imageType: { type: 'enum', value: imageType }, - }, - }, - }, - ); - - const generateDeleteMutation = (imageType: SeasonImageType): string => - generateUpdateGQLFragment( - 'deleteSeasonsImageBySeasonIdAndImageType', - { - input: { seasonId, imageType: { type: 'enum', value: imageType } }, - }, - ); - - const generateUpdateMutation = ( - imageId: string, - imageType: SeasonImageType, - ): string => - generateUpdateGQLFragment( - 'updateSeasonsImageBySeasonIdAndImageType', - { - input: { - patch: { imageId }, - seasonId, - imageType: { type: 'enum', value: imageType }, - }, - }, - ); - - Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { - const [imgId] = imageId; - const [initialValue] = initialData?.data?.[imageType]; - const [currentValue] = formData[imageType]; - - switch (true) { - case initialValue === undefined && currentValue !== undefined: - mutations.push( - `assign${idx}:${generateCreateMutation( - imgId, - imageType as SeasonImageType, - )}`, - ); - break; - case initialValue !== undefined && currentValue === undefined: - mutations.push( - `assign${idx}:${generateDeleteMutation( - imageType as SeasonImageType, - )}`, - ); - break; - case initialValue !== currentValue: - mutations.push( - `assign${idx}:${generateUpdateMutation( - currentValue, - imageType as SeasonImageType, - )}`, - ); - break; - default: - break; - } - }); - - const GqlDoc = gql`mutation ImageAssignments { - ${mutations} - }`; - - await client.mutate({ mutation: GqlDoc }); - }, - [seasonId], - ); - - const { ImageSelectField } = useContext(ExtensionsContext); - - return ( - - defaultTitle="Image Management" - initialData={{ - data: initialImages ?? {}, - loading, - entityNotFound: data?.season === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -/** - * Creates the initial image type values - * @param nodes data nodes - */ -const useImageTypes = ( - nodes: ImageNodes[] = [], -): { - readonly initialImages: FormData; -} => { - const [initialImages, setInitialImages] = useState(getImageTypes()); - - // set all currently assigned images on the server - useEffect(() => { - if (nodes.length > 0) { - let temp = {} as FormData; - - for (const { imageType, imageId } of nodes) { - temp = { ...temp, [imageType]: [imageId] }; - } - - setInitialImages((prevState) => { - return { ...prevState, ...temp }; - }); - } - }, [nodes]); - - return { initialImages } as const; -}; - -/** - * Returns an image type with an empty array(value) using the SeasonImageType enum - */ -const getImageTypes = (): FormData => { - let types = {} as FormData; - - Object.keys(SeasonImageType).map((type) => { - const field = SeasonImageType[type]; - types = { ...types, [field]: [] }; - }); + const { seasonId } = useParams<{ + seasonId: string; + }>(); - return types; + return ; }; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementForm.tsx b/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementForm.tsx new file mode 100644 index 00000000..92dccf8e --- /dev/null +++ b/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementForm.tsx @@ -0,0 +1,219 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + Mutation, + MutationCreateSeasonsImageArgs, + MutationDeleteSeasonsImageBySeasonIdAndImageTypeArgs, + MutationUpdateSeasonsImageBySeasonIdAndImageTypeArgs, + SeasonImageType, + SeasonsImage, + useSeasonImagesQuery, +} from '../../../generated/graphql'; + +interface SeasonImageManagementFormProps { + seasonId: number; +} + +type ImageNodes = Pick & { + __typename: 'SeasonsImage'; +}; + +type FormData = Record; + +const Form: React.FC<{ imageSelectField: unknown }> = ({ + imageSelectField, +}) => { + return ( + <> + {Object.keys(SeasonImageType).map((type) => { + const field = SeasonImageType[type]; + return ( + + ); + })} + + ); +}; + +export const SeasonImageManagementForm: React.FC< + SeasonImageManagementFormProps +> = ({ seasonId }) => { + const { loading, data, error } = useSeasonImagesQuery({ + client, + variables: { id: seasonId }, + fetchPolicy: 'no-cache', + }); + + const { initialImages } = useImageTypes( + data?.season?.seasonsImages.nodes as ImageNodes[], + ); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const mutations: string[] = []; + + const generateCreateMutation = ( + imageId: string, + imageType: SeasonImageType, + ): string => + generateUpdateGQLFragment( + 'createSeasonsImage', + { + input: { + seasonsImage: { + seasonId, + imageId, + imageType: { type: 'enum', value: imageType }, + }, + }, + }, + ); + + const generateDeleteMutation = (imageType: SeasonImageType): string => + generateUpdateGQLFragment( + 'deleteSeasonsImageBySeasonIdAndImageType', + { + input: { seasonId, imageType: { type: 'enum', value: imageType } }, + }, + ); + + const generateUpdateMutation = ( + imageId: string, + imageType: SeasonImageType, + ): string => + generateUpdateGQLFragment( + 'updateSeasonsImageBySeasonIdAndImageType', + { + input: { + patch: { imageId }, + seasonId, + imageType: { type: 'enum', value: imageType }, + }, + }, + ); + + Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { + const [imgId] = imageId; + const [initialValue] = initialData?.data?.[imageType]; + const [currentValue] = formData[imageType]; + + switch (true) { + case initialValue === undefined && currentValue !== undefined: + mutations.push( + `assign${idx}:${generateCreateMutation( + imgId, + imageType as SeasonImageType, + )}`, + ); + break; + case initialValue !== undefined && currentValue === undefined: + mutations.push( + `assign${idx}:${generateDeleteMutation( + imageType as SeasonImageType, + )}`, + ); + break; + case initialValue !== currentValue: + mutations.push( + `assign${idx}:${generateUpdateMutation( + currentValue, + imageType as SeasonImageType, + )}`, + ); + break; + default: + break; + } + }); + + const GqlDoc = gql`mutation ImageAssignments { + ${mutations} + }`; + + await client.mutate({ mutation: GqlDoc }); + }, + [seasonId], + ); + + const { ImageSelectField } = useContext(ExtensionsContext); + + return ( + + defaultTitle="Image Management" + initialData={{ + data: initialImages ?? {}, + loading, + entityNotFound: data?.season === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +/** + * Creates the initial image type values + * @param nodes data nodes + */ +const useImageTypes = ( + nodes: ImageNodes[] = [], +): { + readonly initialImages: FormData; +} => { + const [initialImages, setInitialImages] = useState(getImageTypes()); + + // set all currently assigned images on the server + useEffect(() => { + if (nodes.length > 0) { + let temp = {} as FormData; + + for (const { imageType, imageId } of nodes) { + temp = { ...temp, [imageType]: [imageId] }; + } + + setInitialImages((prevState) => { + return { ...prevState, ...temp }; + }); + } + }, [nodes]); + + return { initialImages } as const; +}; + +/** + * Returns an image type with an empty array(value) using the SeasonImageType enum + */ +const getImageTypes = (): FormData => { + let types = {} as FormData; + + Object.keys(SeasonImageType).map((type) => { + const field = SeasonImageType[type]; + types = { ...types, [field]: [] }; + }); + + return types; +}; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementQuickEdit.tsx new file mode 100644 index 00000000..fa93ca3c --- /dev/null +++ b/services/media/workflows/src/Stations/Seasons/SeasonImageManagement/SeasonImageManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { SeasonData } from '../SeasonExplorerBase/SeasonExplorer.types'; +import { SeasonImageManagementForm } from './SeasonImageManagementForm'; + +export const SeasonImageManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagement.tsx b/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagement.tsx index baf773f9..6187f9cf 100644 --- a/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagement.tsx +++ b/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagement.tsx @@ -1,115 +1,11 @@ -import { ID } from '@axinom/mosaic-managed-workflow-integration'; -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutations, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; -import React, { useCallback, useContext } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - Mutation, - MutationCreateSeasonsTrailerArgs, - MutationDeleteSeasonsTrailerArgs, - useSeasonVideosQuery, -} from '../../../generated/graphql'; - -interface FormData { - trailerVideos: ID[]; -} - -const seasonVideoManagementSchema = Yup.object().shape< - ObjectSchemaDefinition ->({ - trailerVideos: Yup.array().of(Yup.mixed()), -}); +import { SeasonVideoManagementForm } from './SeasonVideoManagementForm'; export const SeasonVideoManagement: React.FC = () => { - const seasonId = Number( - useParams<{ - seasonId: string; - }>().seasonId, - ); - - const { VideoSelectField } = useContext(ExtensionsContext); - - const { loading, data, error } = useSeasonVideosQuery({ - client, - variables: { id: seasonId }, - fetchPolicy: 'no-cache', - }); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const trailerAssignmentMutations = generateArrayMutations({ - current: formData.trailerVideos, - original: initialData.data?.trailerVideos, - generateCreateMutation: (videoId) => - generateUpdateGQLFragment( - 'createSeasonsTrailer', - { input: { seasonsTrailer: { videoId, seasonId } } }, - ), - generateDeleteMutation: (videoId) => - generateUpdateGQLFragment( - 'deleteSeasonsTrailer', - { input: { seasonId, videoId } }, - ), - }); - const GqlMutationDocument = gql`mutation UpdateSeasonVideos { - ${trailerAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [seasonId], - ); - - return ( - - defaultTitle="Video Management" - validationSchema={seasonVideoManagementSchema} - initialData={{ - data: { - trailerVideos: - data?.season?.seasonsTrailers.nodes.map( - (trailer) => trailer.videoId, - ) ?? [], - }, - loading, - entityNotFound: data?.season === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; + const { seasonId } = useParams<{ + seasonId: string; + }>(); -const Form: React.FC<{ videoSelectField: unknown }> = ({ - videoSelectField, -}) => { - return ( - <> - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementForm.tsx b/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementForm.tsx new file mode 100644 index 00000000..c54b210a --- /dev/null +++ b/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementForm.tsx @@ -0,0 +1,114 @@ +import { ID } from '@axinom/mosaic-managed-workflow-integration'; +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutations, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; +import React, { useCallback, useContext } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + Mutation, + MutationCreateSeasonsTrailerArgs, + MutationDeleteSeasonsTrailerArgs, + useSeasonVideosQuery, +} from '../../../generated/graphql'; + +interface SeasonVideoManagementFormProps { + seasonId: number; +} + +interface FormData { + trailerVideos: ID[]; +} + +const seasonVideoManagementSchema = Yup.object().shape< + ObjectSchemaDefinition +>({ + trailerVideos: Yup.array().of(Yup.mixed()), +}); + +export const SeasonVideoManagementForm: React.FC< + SeasonVideoManagementFormProps +> = ({ seasonId }) => { + const { VideoSelectField } = useContext(ExtensionsContext); + + const { loading, data, error } = useSeasonVideosQuery({ + client, + variables: { id: seasonId }, + fetchPolicy: 'no-cache', + }); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const trailerAssignmentMutations = generateArrayMutations({ + current: formData.trailerVideos, + original: initialData.data?.trailerVideos, + generateCreateMutation: (videoId) => + generateUpdateGQLFragment( + 'createSeasonsTrailer', + { input: { seasonsTrailer: { videoId, seasonId } } }, + ), + generateDeleteMutation: (videoId) => + generateUpdateGQLFragment( + 'deleteSeasonsTrailer', + { input: { seasonId, videoId } }, + ), + }); + const GqlMutationDocument = gql`mutation UpdateSeasonVideos { + ${trailerAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [seasonId], + ); + + return ( + + defaultTitle="Video Management" + validationSchema={seasonVideoManagementSchema} + initialData={{ + data: { + trailerVideos: + data?.season?.seasonsTrailers.nodes.map( + (trailer) => trailer.videoId, + ) ?? [], + }, + loading, + entityNotFound: data?.season === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC<{ videoSelectField: unknown }> = ({ + videoSelectField, +}) => { + return ( + <> + + + ); +}; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementQuickEdit.tsx b/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementQuickEdit.tsx new file mode 100644 index 00000000..26b51ca8 --- /dev/null +++ b/services/media/workflows/src/Stations/Seasons/SeasonVideoManagement/SeasonVideoManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { SeasonData } from '../SeasonExplorerBase/SeasonExplorer.types'; +import { SeasonVideoManagementForm } from './SeasonVideoManagementForm'; + +export const SeasonVideoManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/Seasons/SeasonsExplorer/Seasons.tsx b/services/media/workflows/src/Stations/Seasons/SeasonsExplorer/Seasons.tsx index 34971771..175d7580 100644 --- a/services/media/workflows/src/Stations/Seasons/SeasonsExplorer/Seasons.tsx +++ b/services/media/workflows/src/Stations/Seasons/SeasonsExplorer/Seasons.tsx @@ -1,5 +1,9 @@ import React from 'react'; +import { SeasonDetailsQuickEdit } from '../SeasonDetails/SeasonDetailsQuickEdit'; +import { SeasonEpisodeManagementQuickEdit } from '../SeasonEpisodeManagement/SeasonEpisodeManagementQuickEdit'; import { SeasonExplorer } from '../SeasonExplorerBase/SeasonExplorer'; +import { SeasonImageManagementQuickEdit } from '../SeasonImageManagement/SeasonImageManagementQuickEdit'; +import { SeasonVideoManagementQuickEdit } from '../SeasonVideoManagement/SeasonVideoManagementQuickEdit'; import { useSeasonsActions } from './Seasons.actions'; export const Seasons: React.FC = () => { @@ -13,6 +17,24 @@ export const Seasons: React.FC = () => { bulkActions={bulkActions} calculateNavigateUrl={(item) => `/seasons/${item.id}`} onCreateAction="/seasons/create" + quickEditRegistrations={[ + { component: , label: 'Season Details' }, + { + component: , + label: 'Manage Episodes', + generateDetailsLink: (item) => `/seasons/${item.id}/episodes`, + }, + { + component: , + label: 'Manage Videos', + generateDetailsLink: (item) => `/seasons/${item.id}/videos`, + }, + { + component: , + label: 'Manage Images', + generateDetailsLink: (item) => `/seasons/${item.id}/images`, + }, + ]} /> ); }; diff --git a/services/media/workflows/src/Stations/Seasons/registrations.tsx b/services/media/workflows/src/Stations/Seasons/registrations.tsx index 9bf9e928..26f7d2a4 100644 --- a/services/media/workflows/src/Stations/Seasons/registrations.tsx +++ b/services/media/workflows/src/Stations/Seasons/registrations.tsx @@ -65,10 +65,20 @@ export function register(app: PiletApi, extensions: Extensions): void { categoryName: 'Content', }); - app.registerPage('/seasons', Seasons, { - breadcrumb: () => 'Seasons', - permissions: { 'media-service': ['ADMIN', 'TVSHOWS_EDIT', 'TVSHOWS_VIEW'] }, - }); + app.registerPage( + '/seasons', + () => ( + + + + ), + { + breadcrumb: () => 'Seasons', + permissions: { + 'media-service': ['ADMIN', 'TVSHOWS_EDIT', 'TVSHOWS_VIEW'], + }, + }, + ); app.registerPage('/seasons/create', SeasonCreate, { breadcrumb: () => 'New Season', diff --git a/services/media/workflows/src/Stations/TvShows/TvShowExplorerBase/TvShowExplorer.tsx b/services/media/workflows/src/Stations/TvShows/TvShowExplorerBase/TvShowExplorer.tsx index f8d02409..0d448bc0 100644 --- a/services/media/workflows/src/Stations/TvShows/TvShowExplorerBase/TvShowExplorer.tsx +++ b/services/media/workflows/src/Stations/TvShows/TvShowExplorerBase/TvShowExplorer.tsx @@ -28,7 +28,6 @@ import { useUnpublishTvShowMutation, } from '../../../generated/graphql'; import { PublishStatusStateMap } from '../../../Util/PublishStatusStateMap/PublishStatusStateMap'; -import { TvShowDetailsQuickEdit } from '../TvShowDetails/TvShowDetailsQuickEdit'; import { useTvShowsFilters } from './TvShowExplorer.filters'; import { TvShowData, TvShowExplorerProps } from './TvShowExplorer.types'; @@ -196,9 +195,6 @@ export const TvShowExplorer: React.FC = (props) => { filterOptions={filterOptions} defaultSortOrder={{ column: 'updatedDate', direction: 'desc' }} inlineMenuActions={generateInlineMenuActions} - quickEditRegistrations={[ - { component: , label: 'TV Show Details' }, - ]} /> ); case 'SelectionExplorer': diff --git a/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagement.tsx b/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagement.tsx index 61563180..69e865c1 100644 --- a/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagement.tsx +++ b/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagement.tsx @@ -1,52 +1,6 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - Mutation, - MutationCreateTvshowsImageArgs, - MutationDeleteTvshowsImageByTvshowIdAndImageTypeArgs, - MutationUpdateTvshowsImageByTvshowIdAndImageTypeArgs, - TvshowImageType, - TvshowsImage, - useTvshowImagesQuery, -} from '../../../generated/graphql'; - -type ImageNodes = Pick & { - __typename: 'TvshowsImage'; -}; - -type FormData = Record; - -const Form: React.FC<{ imageSelectField: unknown }> = ({ - imageSelectField, -}) => { - return ( - <> - {Object.keys(TvshowImageType).map((type) => { - const field = TvshowImageType[type]; - return ( - - ); - })} - - ); -}; +import { TvShowImageManagementForm } from './TvShowImageManagementForm'; export const TvShowImageManagement: React.FC = () => { const tvshowId = Number( @@ -55,166 +9,5 @@ export const TvShowImageManagement: React.FC = () => { }>().tvshowId, ); - const { loading, data, error } = useTvshowImagesQuery({ - client, - variables: { id: tvshowId }, - fetchPolicy: 'no-cache', - }); - - const { initialImages } = useImageTypes( - data?.tvshow?.tvshowsImages.nodes as ImageNodes[], - ); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const mutations: string[] = []; - - const generateCreateMutation = ( - imageId: string, - imageType: TvshowImageType, - ): string => - generateUpdateGQLFragment( - 'createTvshowsImage', - { - input: { - tvshowsImage: { - tvshowId, - imageId, - imageType: { type: 'enum', value: imageType }, - }, - }, - }, - ); - - const generateDeleteMutation = (imageType: TvshowImageType): string => - generateUpdateGQLFragment( - 'deleteTvshowsImageByTvshowIdAndImageType', - { - input: { tvshowId, imageType: { type: 'enum', value: imageType } }, - }, - ); - - const generateUpdateMutation = ( - imageId: string, - imageType: TvshowImageType, - ): string => - generateUpdateGQLFragment( - 'updateTvshowsImageByTvshowIdAndImageType', - { - input: { - patch: { imageId }, - tvshowId, - imageType: { type: 'enum', value: imageType }, - }, - }, - ); - - Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { - const [imgId] = imageId; - const [initialValue] = initialData?.data?.[imageType]; - const [currentValue] = formData[imageType]; - - switch (true) { - case initialValue === undefined && currentValue !== undefined: - mutations.push( - `assign${idx}:${generateCreateMutation( - imgId, - imageType as TvshowImageType, - )}`, - ); - break; - case initialValue !== undefined && currentValue === undefined: - mutations.push( - `assign${idx}:${generateDeleteMutation( - imageType as TvshowImageType, - )}`, - ); - break; - case initialValue !== currentValue: - mutations.push( - `assign${idx}:${generateUpdateMutation( - currentValue, - imageType as TvshowImageType, - )}`, - ); - break; - default: - break; - } - }); - - const GqlDoc = gql`mutation ImageAssignments { - ${mutations} - }`; - - await client.mutate({ mutation: GqlDoc }); - }, - [tvshowId], - ); - - const { ImageSelectField } = useContext(ExtensionsContext); - - return ( - - defaultTitle="Image Management" - initialData={{ - data: initialImages ?? {}, - loading, - entityNotFound: data?.tvshow === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -/** - * Creates the initial image type values - * @param nodes data nodes - */ -const useImageTypes = ( - nodes: ImageNodes[] = [], -): { - readonly initialImages: FormData; -} => { - const [initialImages, setInitialImages] = useState(getImageTypes()); - - // set all currently assigned images on the server - useEffect(() => { - if (nodes.length > 0) { - let temp = {} as FormData; - - for (const { imageType, imageId } of nodes) { - temp = { ...temp, [imageType]: [imageId] }; - } - - setInitialImages((prevState) => { - return { ...prevState, ...temp }; - }); - } - }, [nodes]); - - return { initialImages } as const; -}; - -/** - * Returns an image type with an empty array(value) using the TvshowImageType enum - */ -const getImageTypes = (): FormData => { - let types = {} as FormData; - - Object.keys(TvshowImageType).map((type) => { - const field = TvshowImageType[type]; - types = { ...types, [field]: [] }; - }); - - return types; + return ; }; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementForm.tsx b/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementForm.tsx new file mode 100644 index 00000000..c04163b2 --- /dev/null +++ b/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementForm.tsx @@ -0,0 +1,219 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + Mutation, + MutationCreateTvshowsImageArgs, + MutationDeleteTvshowsImageByTvshowIdAndImageTypeArgs, + MutationUpdateTvshowsImageByTvshowIdAndImageTypeArgs, + TvshowImageType, + TvshowsImage, + useTvshowImagesQuery, +} from '../../../generated/graphql'; + +interface TvShowImageManagementFormProps { + tvshowId: number; +} + +type ImageNodes = Pick & { + __typename: 'TvshowsImage'; +}; + +type FormData = Record; + +const Form: React.FC<{ imageSelectField: unknown }> = ({ + imageSelectField, +}) => { + return ( + <> + {Object.keys(TvshowImageType).map((type) => { + const field = TvshowImageType[type]; + return ( + + ); + })} + + ); +}; + +export const TvShowImageManagementForm: React.FC< + TvShowImageManagementFormProps +> = ({ tvshowId }) => { + const { loading, data, error } = useTvshowImagesQuery({ + client, + variables: { id: tvshowId }, + fetchPolicy: 'no-cache', + }); + + const { initialImages } = useImageTypes( + data?.tvshow?.tvshowsImages.nodes as ImageNodes[], + ); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const mutations: string[] = []; + + const generateCreateMutation = ( + imageId: string, + imageType: TvshowImageType, + ): string => + generateUpdateGQLFragment( + 'createTvshowsImage', + { + input: { + tvshowsImage: { + tvshowId, + imageId, + imageType: { type: 'enum', value: imageType }, + }, + }, + }, + ); + + const generateDeleteMutation = (imageType: TvshowImageType): string => + generateUpdateGQLFragment( + 'deleteTvshowsImageByTvshowIdAndImageType', + { + input: { tvshowId, imageType: { type: 'enum', value: imageType } }, + }, + ); + + const generateUpdateMutation = ( + imageId: string, + imageType: TvshowImageType, + ): string => + generateUpdateGQLFragment( + 'updateTvshowsImageByTvshowIdAndImageType', + { + input: { + patch: { imageId }, + tvshowId, + imageType: { type: 'enum', value: imageType }, + }, + }, + ); + + Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => { + const [imgId] = imageId; + const [initialValue] = initialData?.data?.[imageType]; + const [currentValue] = formData[imageType]; + + switch (true) { + case initialValue === undefined && currentValue !== undefined: + mutations.push( + `assign${idx}:${generateCreateMutation( + imgId, + imageType as TvshowImageType, + )}`, + ); + break; + case initialValue !== undefined && currentValue === undefined: + mutations.push( + `assign${idx}:${generateDeleteMutation( + imageType as TvshowImageType, + )}`, + ); + break; + case initialValue !== currentValue: + mutations.push( + `assign${idx}:${generateUpdateMutation( + currentValue, + imageType as TvshowImageType, + )}`, + ); + break; + default: + break; + } + }); + + const GqlDoc = gql`mutation ImageAssignments { + ${mutations} + }`; + + await client.mutate({ mutation: GqlDoc }); + }, + [tvshowId], + ); + + const { ImageSelectField } = useContext(ExtensionsContext); + + return ( + + defaultTitle="Image Management" + initialData={{ + data: initialImages ?? {}, + loading, + entityNotFound: data?.tvshow === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +/** + * Creates the initial image type values + * @param nodes data nodes + */ +const useImageTypes = ( + nodes: ImageNodes[] = [], +): { + readonly initialImages: FormData; +} => { + const [initialImages, setInitialImages] = useState(getImageTypes()); + + // set all currently assigned images on the server + useEffect(() => { + if (nodes.length > 0) { + let temp = {} as FormData; + + for (const { imageType, imageId } of nodes) { + temp = { ...temp, [imageType]: [imageId] }; + } + + setInitialImages((prevState) => { + return { ...prevState, ...temp }; + }); + } + }, [nodes]); + + return { initialImages } as const; +}; + +/** + * Returns an image type with an empty array(value) using the TvshowImageType enum + */ +const getImageTypes = (): FormData => { + let types = {} as FormData; + + Object.keys(TvshowImageType).map((type) => { + const field = TvshowImageType[type]; + types = { ...types, [field]: [] }; + }); + + return types; +}; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementQuickEdit.tsx b/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementQuickEdit.tsx new file mode 100644 index 00000000..8060f3fc --- /dev/null +++ b/services/media/workflows/src/Stations/TvShows/TvShowImageManagement/TvShowImageManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { TvShowData } from '../TvShowExplorerBase/TvShowExplorer.types'; +import { TvShowImageManagementForm } from './TvShowImageManagementForm'; + +export const TvShowImageManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagement.tsx b/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagement.tsx index d03bedfb..81462c97 100644 --- a/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagement.tsx +++ b/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagement.tsx @@ -1,33 +1,6 @@ -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutations, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; -import React, { useCallback } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { - Mutation, - MutationUpdateSeasonArgs, - useTvShowSeasonsQuery, -} from '../../../generated/graphql'; -import { SeasonSelectField } from './SeasonSelectField/SeasonSelectField'; -import { TvShowSeason } from './TvShowSeasonManagement.types'; - -interface FormData { - seasons: TvShowSeason[]; -} - -const tvShowSeasonManagementSchema = Yup.object< - ObjectSchemaDefinition ->({ - seasons: Yup.array().of(Yup.object({ id: Yup.number() })), -}); +import { TvShowSeasonManagementForm } from './TvShowSeasonManagementForm'; export const TvShowSeasonManagement: React.FC = () => { const tvshowId = Number( @@ -36,65 +9,5 @@ export const TvShowSeasonManagement: React.FC = () => { }>().tvshowId, ); - const { loading, data, error } = useTvShowSeasonsQuery({ - client, - variables: { id: tvshowId }, - fetchPolicy: 'no-cache', - }); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const seasonAssignmentMutations = generateArrayMutations({ - current: formData.seasons, - original: initialData.data?.seasons, - generateCreateMutation: ({ id }) => - generateUpdateGQLFragment('updateSeason', { - input: { id, patch: { tvshowId } }, - }), - generateDeleteMutation: ({ id }) => - generateUpdateGQLFragment('updateSeason', { - input: { id, patch: { tvshowId: null } }, - }), - }); - - const GqlMutationDocument = gql`mutation UpdateTvShowSeasons { - ${seasonAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [tvshowId], - ); - - return ( - - defaultTitle="Season Management" - validationSchema={tvShowSeasonManagementSchema} - initialData={{ - data: { - seasons: data?.tvshow?.seasons.nodes ?? [], - }, - loading, - entityNotFound: data?.tvshow === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -const Form: React.FC = () => { - return ( - <> - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementForm.tsx b/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementForm.tsx new file mode 100644 index 00000000..e5e60e54 --- /dev/null +++ b/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementForm.tsx @@ -0,0 +1,99 @@ +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutations, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; +import React, { useCallback } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { + Mutation, + MutationUpdateSeasonArgs, + useTvShowSeasonsQuery, +} from '../../../generated/graphql'; +import { SeasonSelectField } from './SeasonSelectField/SeasonSelectField'; +import { TvShowSeason } from './TvShowSeasonManagement.types'; + +interface TvShowSeasonManagementFormProps { + tvshowId: number; +} + +interface FormData { + seasons: TvShowSeason[]; +} + +const tvShowSeasonManagementSchema = Yup.object< + ObjectSchemaDefinition +>({ + seasons: Yup.array().of(Yup.object({ id: Yup.number() })), +}); + +export const TvShowSeasonManagementForm: React.FC< + TvShowSeasonManagementFormProps +> = ({ tvshowId }) => { + const { loading, data, error } = useTvShowSeasonsQuery({ + client, + variables: { id: tvshowId }, + fetchPolicy: 'no-cache', + }); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const seasonAssignmentMutations = generateArrayMutations({ + current: formData.seasons, + original: initialData.data?.seasons, + generateCreateMutation: ({ id }) => + generateUpdateGQLFragment('updateSeason', { + input: { id, patch: { tvshowId } }, + }), + generateDeleteMutation: ({ id }) => + generateUpdateGQLFragment('updateSeason', { + input: { id, patch: { tvshowId: null } }, + }), + }); + + const GqlMutationDocument = gql`mutation UpdateTvShowSeasons { + ${seasonAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [tvshowId], + ); + + return ( + + defaultTitle="Season Management" + validationSchema={tvShowSeasonManagementSchema} + initialData={{ + data: { + seasons: data?.tvshow?.seasons.nodes ?? [], + }, + loading, + entityNotFound: data?.tvshow === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC = () => { + return ( + <> + + + ); +}; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementQuickEdit.tsx b/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementQuickEdit.tsx new file mode 100644 index 00000000..8b425635 --- /dev/null +++ b/services/media/workflows/src/Stations/TvShows/TvShowSeasonManagement/TvShowSeasonManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { TvShowData } from '../TvShowExplorerBase/TvShowExplorer.types'; +import { TvShowSeasonManagementForm } from './TvShowSeasonManagementForm'; + +export const TvShowSeasonManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagement.tsx b/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagement.tsx index 9b36610e..cc25a52c 100644 --- a/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagement.tsx +++ b/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagement.tsx @@ -1,34 +1,6 @@ -import { ID } from '@axinom/mosaic-managed-workflow-integration'; -import { - createUpdateGQLFragmentGenerator, - Details, - DetailsProps, - generateArrayMutations, -} from '@axinom/mosaic-ui'; -import { Field } from 'formik'; -import gql from 'graphql-tag'; -import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; -import React, { useCallback, useContext } from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; -import * as Yup from 'yup'; -import { client } from '../../../apolloClient'; -import { ExtensionsContext } from '../../../externals'; -import { - Mutation, - MutationCreateTvshowsTrailerArgs, - MutationDeleteTvshowsTrailerArgs, - useTvShowVideosQuery, -} from '../../../generated/graphql'; - -interface FormData { - trailerVideos: ID[]; -} - -const tvShowVideoManagementSchema = Yup.object< - ObjectSchemaDefinition ->({ - trailerVideos: Yup.array().of(Yup.mixed()), -}); +import { TvShowVideoManagementForm } from './TvShowVideoManagementForm'; export const TvShowVideoManagement: React.FC = () => { const tvshowId = Number( @@ -37,80 +9,5 @@ export const TvShowVideoManagement: React.FC = () => { }>().tvshowId, ); - const { VideoSelectField } = useContext(ExtensionsContext); - - const { loading, data, error } = useTvShowVideosQuery({ - client, - variables: { id: tvshowId }, - fetchPolicy: 'no-cache', - }); - - const onSubmit = useCallback( - async ( - formData: FormData, - initialData: DetailsProps['initialData'], - ): Promise => { - const generateUpdateGQLFragment = - createUpdateGQLFragmentGenerator(); - - const trailerAssignmentMutations = generateArrayMutations({ - current: formData.trailerVideos, - original: initialData.data?.trailerVideos, - generateCreateMutation: (videoId) => - generateUpdateGQLFragment( - 'createTvshowsTrailer', - { input: { tvshowsTrailer: { videoId, tvshowId } } }, - ), - generateDeleteMutation: (videoId) => - generateUpdateGQLFragment( - 'deleteTvshowsTrailer', - { input: { tvshowId, videoId } }, - ), - }); - - const GqlMutationDocument = gql`mutation UpdateTvShowsVideos { - ${trailerAssignmentMutations} - }`; - - await client.mutate({ mutation: GqlMutationDocument }); - }, - [tvshowId], - ); - - return ( - - defaultTitle="Video Management" - validationSchema={tvShowVideoManagementSchema} - initialData={{ - data: { - trailerVideos: - data?.tvshow?.tvshowsTrailers.nodes.map( - (trailer) => trailer.videoId, - ) ?? [], - }, - loading, - entityNotFound: data?.tvshow === null, - error: error?.message, - }} - saveData={onSubmit} - > - - - ); -}; - -const Form: React.FC<{ videoSelectField: unknown }> = ({ - videoSelectField, -}) => { - return ( - <> - - - ); + return ; }; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementForm.tsx b/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementForm.tsx new file mode 100644 index 00000000..825bc78d --- /dev/null +++ b/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementForm.tsx @@ -0,0 +1,114 @@ +import { ID } from '@axinom/mosaic-managed-workflow-integration'; +import { + createUpdateGQLFragmentGenerator, + Details, + DetailsProps, + generateArrayMutations, +} from '@axinom/mosaic-ui'; +import { Field } from 'formik'; +import gql from 'graphql-tag'; +import { ObjectSchemaDefinition } from 'ObjectSchemaDefinition'; +import React, { useCallback, useContext } from 'react'; +import * as Yup from 'yup'; +import { client } from '../../../apolloClient'; +import { ExtensionsContext } from '../../../externals'; +import { + Mutation, + MutationCreateTvshowsTrailerArgs, + MutationDeleteTvshowsTrailerArgs, + useTvShowVideosQuery, +} from '../../../generated/graphql'; + +interface TvShowVideoManagementFormProps { + tvshowId: number; +} +interface FormData { + trailerVideos: ID[]; +} + +const tvShowVideoManagementSchema = Yup.object< + ObjectSchemaDefinition +>({ + trailerVideos: Yup.array().of(Yup.mixed()), +}); + +export const TvShowVideoManagementForm: React.FC< + TvShowVideoManagementFormProps +> = ({ tvshowId }) => { + const { VideoSelectField } = useContext(ExtensionsContext); + + const { loading, data, error } = useTvShowVideosQuery({ + client, + variables: { id: tvshowId }, + fetchPolicy: 'no-cache', + }); + + const onSubmit = useCallback( + async ( + formData: FormData, + initialData: DetailsProps['initialData'], + ): Promise => { + const generateUpdateGQLFragment = + createUpdateGQLFragmentGenerator(); + + const trailerAssignmentMutations = generateArrayMutations({ + current: formData.trailerVideos, + original: initialData.data?.trailerVideos, + generateCreateMutation: (videoId) => + generateUpdateGQLFragment( + 'createTvshowsTrailer', + { input: { tvshowsTrailer: { videoId, tvshowId } } }, + ), + generateDeleteMutation: (videoId) => + generateUpdateGQLFragment( + 'deleteTvshowsTrailer', + { input: { tvshowId, videoId } }, + ), + }); + + const GqlMutationDocument = gql`mutation UpdateTvShowsVideos { + ${trailerAssignmentMutations} + }`; + + await client.mutate({ mutation: GqlMutationDocument }); + }, + [tvshowId], + ); + + return ( + + defaultTitle="Video Management" + validationSchema={tvShowVideoManagementSchema} + initialData={{ + data: { + trailerVideos: + data?.tvshow?.tvshowsTrailers.nodes.map( + (trailer) => trailer.videoId, + ) ?? [], + }, + loading, + entityNotFound: data?.tvshow === null, + error: error?.message, + }} + saveData={onSubmit} + > + + + ); +}; + +const Form: React.FC<{ videoSelectField: unknown }> = ({ + videoSelectField, +}) => { + return ( + <> + + + ); +}; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementQuickEdit.tsx b/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementQuickEdit.tsx new file mode 100644 index 00000000..4746bfbc --- /dev/null +++ b/services/media/workflows/src/Stations/TvShows/TvShowVideoManagement/TvShowVideoManagementQuickEdit.tsx @@ -0,0 +1,13 @@ +import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui'; +import React, { useContext } from 'react'; +import { TvShowData } from '../TvShowExplorerBase/TvShowExplorer.types'; +import { TvShowVideoManagementForm } from './TvShowVideoManagementForm'; + +export const TvShowVideoManagementQuickEdit: React.FC = () => { + const { selectedItem } = + useContext>(QuickEditContext); + + return selectedItem ? ( + + ) : null; +}; diff --git a/services/media/workflows/src/Stations/TvShows/TvShowsExplorer/TvShows.tsx b/services/media/workflows/src/Stations/TvShows/TvShowsExplorer/TvShows.tsx index d286b6fb..95d18acf 100644 --- a/services/media/workflows/src/Stations/TvShows/TvShowsExplorer/TvShows.tsx +++ b/services/media/workflows/src/Stations/TvShows/TvShowsExplorer/TvShows.tsx @@ -1,5 +1,9 @@ import React from 'react'; +import { TvShowDetailsQuickEdit } from '../TvShowDetails/TvShowDetailsQuickEdit'; import { TvShowExplorer } from '../TvShowExplorerBase/TvShowExplorer'; +import { TvShowImageManagementQuickEdit } from '../TvShowImageManagement/TvShowImageManagementQuickEdit'; +import { TvShowSeasonManagementQuickEdit } from '../TvShowSeasonManagement/TvShowSeasonManagementQuickEdit'; +import { TvShowVideoManagementQuickEdit } from '../TvShowVideoManagement/TvShowVideoManagementQuickEdit'; import { useTvShowsActions } from './TvShows.actions'; export const TvShows: React.FC = () => { @@ -13,6 +17,24 @@ export const TvShows: React.FC = () => { calculateNavigateUrl={(item) => `/tvshows/${item.id}`} onCreateAction="/tvshows/create" bulkActions={bulkActions} + quickEditRegistrations={[ + { component: , label: 'TV Show Details' }, + { + component: , + label: 'Manage Seasons', + generateDetailsLink: (item) => `/tvshows/${item.id}/seasons`, + }, + { + component: , + label: 'Manage Trailers', + generateDetailsLink: (item) => `/tvshows/${item.id}/videos`, + }, + { + component: , + label: 'Manage Images', + generateDetailsLink: (item) => `/tvshows/${item.id}/images`, + }, + ]} /> ); }; diff --git a/services/media/workflows/src/Stations/TvShows/registrations.tsx b/services/media/workflows/src/Stations/TvShows/registrations.tsx index c7b68bf6..65654bfe 100644 --- a/services/media/workflows/src/Stations/TvShows/registrations.tsx +++ b/services/media/workflows/src/Stations/TvShows/registrations.tsx @@ -82,10 +82,20 @@ export function register(app: PiletApi, extensions: Extensions): void { categoryName: 'Content', }); - app.registerPage('/tvshows', TvShows, { - breadcrumb: () => 'TV Shows', - permissions: { 'media-service': ['ADMIN', 'TVSHOWS_EDIT', 'TVSHOWS_VIEW'] }, - }); + app.registerPage( + '/tvshows', + () => ( + + + + ), + { + breadcrumb: () => 'TV Shows', + permissions: { + 'media-service': ['ADMIN', 'TVSHOWS_EDIT', 'TVSHOWS_VIEW'], + }, + }, + ); app.registerPage('/tvshows/create', TvShowCreate, { breadcrumb: () => 'New TV Show',