diff --git a/backend/core/management/commands/create_dev_dataset.py b/backend/core/management/commands/create_dev_dataset.py index e46760a6..02443fff 100644 --- a/backend/core/management/commands/create_dev_dataset.py +++ b/backend/core/management/commands/create_dev_dataset.py @@ -19,8 +19,6 @@ ) import random -from django.contrib.contenttypes.models import ContentType - from .fixtures import ( case_study_names, gift_names, diff --git a/backend/core/types/input/DescriptionFieldInputType.py b/backend/core/types/input/DescriptionFieldInputType.py new file mode 100644 index 00000000..5f0d93a8 --- /dev/null +++ b/backend/core/types/input/DescriptionFieldInputType.py @@ -0,0 +1,7 @@ +from graphene import Enum, InputObjectType +from core.models import SourceMention +from core.types.input.FieldInputType import FieldInputType + + +class DescriptionFieldInputType(FieldInputType, InputObjectType): + source_mention = Enum.from_enum(SourceMention)() diff --git a/backend/core/types/input/EntityDescriptionInputType.py b/backend/core/types/input/EntityDescriptionInputType.py new file mode 100644 index 00000000..b5026c5a --- /dev/null +++ b/backend/core/types/input/EntityDescriptionInputType.py @@ -0,0 +1,11 @@ +from graphene import ID, InputObjectType, List, NonNull, String + +from core.types.input.NamedInputType import NamedInputType + + +class EntityDescriptionInputType(NamedInputType, InputObjectType): + book = String() + chapter = String() + page = String() + designators = List(NonNull(String)) + categories = List(NonNull(ID)) diff --git a/backend/core/types/input/FieldInputType.py b/backend/core/types/input/FieldInputType.py new file mode 100644 index 00000000..e831670f --- /dev/null +++ b/backend/core/types/input/FieldInputType.py @@ -0,0 +1,6 @@ +from graphene import InputObjectType, Int, String + + +class FieldInputType(InputObjectType): + certainty = String() + note = String() diff --git a/backend/core/types/input/NamedInputType.py b/backend/core/types/input/NamedInputType.py new file mode 100644 index 00000000..ba3e3eb3 --- /dev/null +++ b/backend/core/types/input/NamedInputType.py @@ -0,0 +1,6 @@ +from graphene import InputObjectType, String + + +class NamedInputType(InputObjectType): + name = String() + description = String() diff --git a/backend/event/mutations/UpdateEpisodeMutation.py b/backend/event/mutations/UpdateEpisodeMutation.py index 06ac5268..995207d0 100644 --- a/backend/event/mutations/UpdateEpisodeMutation.py +++ b/backend/event/mutations/UpdateEpisodeMutation.py @@ -1,21 +1,15 @@ from graphene import ID, Boolean, InputObjectType, List, NonNull, ResolveInfo, String from django.core.exceptions import ObjectDoesNotExist +from core.types.input.EntityDescriptionInputType import EntityDescriptionInputType from event.models import Episode from graphql_app.LettercraftMutation import LettercraftMutation from graphql_app.types.LettercraftErrorType import LettercraftErrorType -class UpdateEpisodeInput(InputObjectType): +class UpdateEpisodeInput(EntityDescriptionInputType, InputObjectType): id = ID(required=True) - name = String() - description = String() - book = String() - chapter = String() - page = String() - designators = List(NonNull(String)) summary = String() - categories = List(NonNull(ID)) class UpdateEpisodeMutation(LettercraftMutation): diff --git a/backend/graphql_app/schema.py b/backend/graphql_app/schema.py index 7ff5499a..1ade2fe6 100644 --- a/backend/graphql_app/schema.py +++ b/backend/graphql_app/schema.py @@ -8,11 +8,18 @@ from source.queries import SourceQueries from space.queries import SpaceQueries from user.queries import UserQueries + from source.mutations.UpdateOrCreateSourceMutation import UpdateOrCreateSourceMutation from event.mutations.UpdateEpisodeMutation import UpdateEpisodeMutation from person.mutations.CreateAgentMutation import CreateAgentMutation from person.mutations.UpdateAgentMutation import UpdateAgentMutation from person.mutations.DeleteAgentMutation import DeleteAgentMutation +from letter.mutations.CreateLetterMutation import CreateLetterMutation +from letter.mutations.DeleteLetterMutation import DeleteLetterMutation +from letter.mutations.UpdateLetterMutation import UpdateLetterMutation +from letter.mutations.CreateGiftMutation import CreateGiftMutation +from letter.mutations.DeleteGiftMutation import DeleteGiftMutation +from letter.mutations.UpdateGiftMutation import UpdateGiftMutation from person.mutations.CreatePersonReferenceMutation import CreatePersonReferenceMutation from person.mutations.UpdatePersonReferenceMutation import UpdatePersonReferenceMutation from person.mutations.DeletePersonReferenceMutation import DeletePersonReferenceMutation @@ -38,6 +45,12 @@ class Mutation(ObjectType): create_agent = CreateAgentMutation.Field() update_agent = UpdateAgentMutation.Field() delete_agent = DeleteAgentMutation.Field() + create_letter = CreateLetterMutation.Field() + update_letter = UpdateLetterMutation.Field() + delete_letter = DeleteLetterMutation.Field() + create_gift = CreateGiftMutation.Field() + update_gift = UpdateGiftMutation.Field() + delete_gift = DeleteGiftMutation.Field() create_person_reference = CreatePersonReferenceMutation.Field() update_person_reference = UpdatePersonReferenceMutation.Field() delete_person_reference = DeletePersonReferenceMutation.Field() diff --git a/backend/letter/mutations/CreateGiftMutation.py b/backend/letter/mutations/CreateGiftMutation.py new file mode 100644 index 00000000..c06dd9aa --- /dev/null +++ b/backend/letter/mutations/CreateGiftMutation.py @@ -0,0 +1,45 @@ +from graphene import ( + ID, + Field, + InputObjectType, + List, + NonNull, + ResolveInfo, + String, +) + +from letter.models import GiftDescription +from letter.types.GiftDescriptionType import GiftDescriptionType +from graphql_app.LettercraftMutation import LettercraftMutation +from graphql_app.types.LettercraftErrorType import LettercraftErrorType +from source.models import Source + + +class CreateGiftInput(InputObjectType): + name = String(required=True) + source = ID(required=True) + + +class CreateGiftMutation(LettercraftMutation): + gift = Field(GiftDescriptionType) + errors = List(NonNull(LettercraftErrorType), required=True) + + django_model = GiftDescription + + class Arguments: + gift_data = CreateGiftInput(required=True) + + @classmethod + def mutate(cls, root: None, info: ResolveInfo, gift_data: CreateGiftInput): + try: + source = Source.objects.get(id=getattr(gift_data, "source")) + except Source.DoesNotExist: + error = LettercraftErrorType(field="source", messages=["Source not found."]) + return cls(errors=[error]) # type: ignore + + letter = GiftDescription.objects.create( + name=getattr(gift_data, "name"), + source=source, + ) + + return cls(Letter=letter, errors=[]) # type: ignore diff --git a/backend/letter/mutations/CreateLetterMutation.py b/backend/letter/mutations/CreateLetterMutation.py new file mode 100644 index 00000000..bf9bc5f8 --- /dev/null +++ b/backend/letter/mutations/CreateLetterMutation.py @@ -0,0 +1,45 @@ +from graphene import ( + ID, + Field, + InputObjectType, + List, + NonNull, + ResolveInfo, + String, +) + +from letter.models import LetterDescription +from letter.types.LetterDescriptionType import LetterDescriptionType +from graphql_app.LettercraftMutation import LettercraftMutation +from graphql_app.types.LettercraftErrorType import LettercraftErrorType +from source.models import Source + + +class CreateLetterInput(InputObjectType): + name = String(required=True) + source = ID(required=True) + + +class CreateLetterMutation(LettercraftMutation): + letter = Field(LetterDescriptionType) + errors = List(NonNull(LettercraftErrorType), required=True) + + django_model = LetterDescription + + class Arguments: + letter_data = CreateLetterInput(required=True) + + @classmethod + def mutate(cls, root: None, info: ResolveInfo, letter_data: CreateLetterInput): + try: + source = Source.objects.get(id=getattr(letter_data, "source")) + except Source.DoesNotExist: + error = LettercraftErrorType(field="source", messages=["Source not found."]) + return cls(errors=[error]) # type: ignore + + letter = LetterDescription.objects.create( + name=getattr(letter_data, "name"), + source=source, + ) + + return cls(Letter=letter, errors=[]) # type: ignore diff --git a/backend/letter/mutations/DeleteGiftMutation.py b/backend/letter/mutations/DeleteGiftMutation.py new file mode 100644 index 00000000..5a380026 --- /dev/null +++ b/backend/letter/mutations/DeleteGiftMutation.py @@ -0,0 +1,26 @@ +from graphene import ID, Boolean, List, Mutation, NonNull, ResolveInfo + +from letter.models import GiftDescription +from letter.types.GiftDescriptionType import GiftDescriptionType +from graphql_app.types.LettercraftErrorType import LettercraftErrorType + + +class DeleteGiftMutation(Mutation): + ok = Boolean(required=True) + errors = List(NonNull(LettercraftErrorType), required=True) + + class Arguments: + id = ID(required=True) + + @classmethod + def mutate(cls, root: None, info: ResolveInfo, id: str): + try: + gift = GiftDescriptionType.get_queryset( + GiftDescription.objects, info + ).get(id=id) + except GiftDescription.DoesNotExist: + error = LettercraftErrorType(field="id", messages=["Gift not found."]) + return cls(ok=False, errors=[error]) # type: ignore + + gift.delete() + return cls(ok=True, errors=[]) # type: ignore diff --git a/backend/letter/mutations/DeleteLetterMutation.py b/backend/letter/mutations/DeleteLetterMutation.py new file mode 100644 index 00000000..b5083304 --- /dev/null +++ b/backend/letter/mutations/DeleteLetterMutation.py @@ -0,0 +1,26 @@ +from graphene import ID, Boolean, List, Mutation, NonNull, ResolveInfo + +from letter.models import LetterDescription +from letter.types.LetterDescriptionType import LetterDescriptionType +from graphql_app.types.LettercraftErrorType import LettercraftErrorType + + +class DeleteLetterMutation(Mutation): + ok = Boolean(required=True) + errors = List(NonNull(LettercraftErrorType), required=True) + + class Arguments: + id = ID(required=True) + + @classmethod + def mutate(cls, root: None, info: ResolveInfo, id: str): + try: + letter = LetterDescriptionType.get_queryset( + LetterDescription.objects, info + ).get(id=id) + except LetterDescription.DoesNotExist: + error = LettercraftErrorType(field="id", messages=["Letter not found."]) + return cls(ok=False, errors=[error]) # type: ignore + + letter.delete() + return cls(ok=True, errors=[]) # type: ignore diff --git a/backend/letter/mutations/UpdateGiftMutation.py b/backend/letter/mutations/UpdateGiftMutation.py new file mode 100644 index 00000000..ffac7f22 --- /dev/null +++ b/backend/letter/mutations/UpdateGiftMutation.py @@ -0,0 +1,55 @@ +from graphene import ID, Boolean, InputObjectType, List, NonNull, ResolveInfo +from django.core.exceptions import ObjectDoesNotExist +from core.types.input.DescriptionFieldInputType import DescriptionFieldInputType +from core.types.input.EntityDescriptionInputType import EntityDescriptionInputType +from letter.models import GiftDescription +from graphql_app.LettercraftMutation import LettercraftMutation + +from graphql_app.types.LettercraftErrorType import LettercraftErrorType + + +class GiftCategorisationInput(DescriptionFieldInputType, InputObjectType): + id = ID() + category = ID(required=True) + + +class UpdateGiftInput(EntityDescriptionInputType, InputObjectType): + id = ID(required=True) + categorisations = List(NonNull(GiftCategorisationInput)) + + +class UpdateGiftMutation(LettercraftMutation): + ok = Boolean(required=True) + errors = List(NonNull(LettercraftErrorType), required=True) + + django_model = GiftDescription + + class Arguments: + gift_data = UpdateGiftInput(required=True) + + @classmethod + def mutate(cls, root: None, info: ResolveInfo, gift_data: UpdateGiftInput): + try: + retrieved_object = cls.get_or_create_object(info, gift_data) + except ObjectDoesNotExist as e: + error = LettercraftErrorType(field="id", messages=[str(e)]) + return cls(ok=False, errors=[error]) # type: ignore + + gift: GiftDescription = retrieved_object.object # type: ignore + + try: + cls.mutate_object(gift_data, gift, info, ["categorisations"]) + except ObjectDoesNotExist as field: + error = LettercraftErrorType( + field=str(field), messages=["Related object cannot be found."] + ) + return cls(ok=False, errors=[error]) # type: ignore + + # TODO: resolve categorisations + + user = info.context.user + gift.contributors.add(user) + + gift.save() + + return cls(ok=True, errors=[]) # type: ignore diff --git a/backend/letter/mutations/UpdateLetterMutation.py b/backend/letter/mutations/UpdateLetterMutation.py new file mode 100644 index 00000000..12484215 --- /dev/null +++ b/backend/letter/mutations/UpdateLetterMutation.py @@ -0,0 +1,55 @@ +from graphene import ID, Boolean, InputObjectType, List, NonNull, ResolveInfo +from django.core.exceptions import ObjectDoesNotExist +from core.types.input.DescriptionFieldInputType import DescriptionFieldInputType +from core.types.input.EntityDescriptionInputType import EntityDescriptionInputType +from letter.models import LetterDescription +from graphql_app.LettercraftMutation import LettercraftMutation + +from graphql_app.types.LettercraftErrorType import LettercraftErrorType + + +class LetterCategorisationInput(DescriptionFieldInputType, InputObjectType): + id = ID() + category = ID(required=True) + + +class UpdateLetterInput(EntityDescriptionInputType, InputObjectType): + id = ID(required=True) + categorisations = List(NonNull(LetterCategorisationInput)) + + +class UpdateLetterMutation(LettercraftMutation): + ok = Boolean(required=True) + errors = List(NonNull(LettercraftErrorType), required=True) + + django_model = LetterDescription + + class Arguments: + letter_data = UpdateLetterInput(required=True) + + @classmethod + def mutate(cls, root: None, info: ResolveInfo, letter_data: UpdateLetterInput): + try: + retrieved_object = cls.get_or_create_object(info, letter_data) + except ObjectDoesNotExist as e: + error = LettercraftErrorType(field="id", messages=[str(e)]) + return cls(ok=False, errors=[error]) # type: ignore + + letter: LetterDescription = retrieved_object.object # type: ignore + + try: + cls.mutate_object(letter_data, letter, info, ["categorisations"]) + except ObjectDoesNotExist as field: + error = LettercraftErrorType( + field=str(field), messages=["Related object cannot be found."] + ) + return cls(ok=False, errors=[error]) # type: ignore + + # TODO: resolve categorisations + + user = info.context.user + letter.contributors.add(user) + + letter.save() + + return cls(ok=True, errors=[]) # type: ignore diff --git a/backend/letter/queries.py b/backend/letter/queries.py index b4c1ccbb..18260b27 100644 --- a/backend/letter/queries.py +++ b/backend/letter/queries.py @@ -1,7 +1,8 @@ from graphene import ID, Field, List, NonNull, ObjectType, ResolveInfo from django.db.models import QuerySet, Q -from letter.models import GiftDescription, LetterDescription +from letter.models import GiftDescription, LetterCategory, LetterDescription +from letter.types.LetterCategoryType import LetterCategoryType from letter.types.GiftDescriptionType import GiftDescriptionType from letter.types.LetterDescriptionType import LetterDescriptionType @@ -13,6 +14,11 @@ class LetterQueries(ObjectType): NonNull(LetterDescriptionType), required=True, episode_id=ID(), source_id=ID() ) + letter_categories = List( + NonNull(LetterCategoryType), + required=True, + ) + gift_description = Field(GiftDescriptionType, id=ID(required=True)) gift_descriptions = List( @@ -47,14 +53,20 @@ def resolve_letter_descriptions( LetterDescription.objects, info ).filter(filters) + @staticmethod + def resolve_letter_categories( + parent: None, info: ResolveInfo + ) -> QuerySet[LetterCategory]: + return LetterCategoryType.get_queryset(LetterCategory.objects, info).all() + @staticmethod def resolve_gift_description( parent: None, info: ResolveInfo, id: str ) -> GiftDescription | None: try: - return GiftDescriptionType.get_queryset( - GiftDescription.objects, info - ).get(id=id) + return GiftDescriptionType.get_queryset(GiftDescription.objects, info).get( + id=id + ) except GiftDescription.DoesNotExist: return None @@ -71,6 +83,6 @@ def resolve_gift_descriptions( if source_id: filters &= Q(source_id=source_id) - return GiftDescriptionType.get_queryset( - GiftDescription.objects, info - ).filter(filters) + return GiftDescriptionType.get_queryset(GiftDescription.objects, info).filter( + filters + ) diff --git a/frontend/generated/graphql.ts b/frontend/generated/graphql.ts index d4b0089e..7910ea5a 100644 --- a/frontend/generated/graphql.ts +++ b/frontend/generated/graphql.ts @@ -103,6 +103,28 @@ export type CreateEpisodeMutation = { errors: Array; }; +export type CreateGiftInput = { + name: Scalars['String']['input']; + source: Scalars['ID']['input']; +}; + +export type CreateGiftMutation = { + __typename?: 'CreateGiftMutation'; + errors: Array; + gift?: Maybe; +}; + +export type CreateLetterInput = { + name: Scalars['String']['input']; + source: Scalars['ID']['input']; +}; + +export type CreateLetterMutation = { + __typename?: 'CreateLetterMutation'; + errors: Array; + letter?: Maybe; +}; + export type CreatePersonReferenceInput = { description: Scalars['ID']['input']; person: Scalars['ID']['input']; @@ -126,6 +148,18 @@ export type DeleteEpisodeMutation = { ok: Scalars['Boolean']['output']; }; +export type DeleteGiftMutation = { + __typename?: 'DeleteGiftMutation'; + errors: Array; + ok: Scalars['Boolean']['output']; +}; + +export type DeleteLetterMutation = { + __typename?: 'DeleteLetterMutation'; + errors: Array; + ok: Scalars['Boolean']['output']; +}; + export type DeletePersonReferenceMutation = { __typename?: 'DeletePersonReferenceMutation'; errors: Array; @@ -191,6 +225,14 @@ export enum Gender { Unknown = 'UNKNOWN' } +export type GiftCategorisationInput = { + category: Scalars['ID']['input']; + certainty?: InputMaybe; + id?: InputMaybe; + note?: InputMaybe; + sourceMention?: InputMaybe; +}; + export type GiftCategoryType = { __typename?: 'GiftCategoryType'; /** Longer description to help identify this object */ @@ -251,6 +293,14 @@ export type HistoricalPersonType = { name: Scalars['String']['output']; }; +export type LetterCategorisationInput = { + category: Scalars['ID']['input']; + certainty?: InputMaybe; + id?: InputMaybe; + note?: InputMaybe; + sourceMention?: InputMaybe; +}; + export type LetterCategoryType = { __typename?: 'LetterCategoryType'; description: Scalars['String']['output']; @@ -358,12 +408,18 @@ export type Mutation = { __typename?: 'Mutation'; createAgent?: Maybe; createEpisode?: Maybe; + createGift?: Maybe; + createLetter?: Maybe; createPersonReference?: Maybe; deleteAgent?: Maybe; deleteEpisode?: Maybe; + deleteGift?: Maybe; + deleteLetter?: Maybe; deletePersonReference?: Maybe; updateAgent?: Maybe; updateEpisode?: Maybe; + updateGift?: Maybe; + updateLetter?: Maybe; updateOrCreateSource?: Maybe; updatePersonReference?: Maybe; }; @@ -379,6 +435,16 @@ export type MutationCreateEpisodeArgs = { }; +export type MutationCreateGiftArgs = { + giftData: CreateGiftInput; +}; + + +export type MutationCreateLetterArgs = { + letterData: CreateLetterInput; +}; + + export type MutationCreatePersonReferenceArgs = { referenceData: CreatePersonReferenceInput; }; @@ -394,6 +460,16 @@ export type MutationDeleteEpisodeArgs = { }; +export type MutationDeleteGiftArgs = { + id: Scalars['ID']['input']; +}; + + +export type MutationDeleteLetterArgs = { + id: Scalars['ID']['input']; +}; + + export type MutationDeletePersonReferenceArgs = { id: Scalars['ID']['input']; }; @@ -409,6 +485,16 @@ export type MutationUpdateEpisodeArgs = { }; +export type MutationUpdateGiftArgs = { + giftData: UpdateGiftInput; +}; + + +export type MutationUpdateLetterArgs = { + letterData: UpdateLetterInput; +}; + + export type MutationUpdateOrCreateSourceArgs = { sourceData: UpdateCreateSourceInput; }; @@ -562,6 +648,7 @@ export type Query = { episodes: Array; giftDescription?: Maybe; giftDescriptions: Array; + letterCategories: Array; letterDescription?: Maybe; letterDescriptions: Array; source: SourceType; @@ -940,6 +1027,42 @@ export type UpdateEpisodeMutation = { ok: Scalars['Boolean']['output']; }; +export type UpdateGiftInput = { + book?: InputMaybe; + categories?: InputMaybe>; + categorisations?: InputMaybe>; + chapter?: InputMaybe; + description?: InputMaybe; + designators?: InputMaybe>; + id: Scalars['ID']['input']; + name?: InputMaybe; + page?: InputMaybe; +}; + +export type UpdateGiftMutation = { + __typename?: 'UpdateGiftMutation'; + errors: Array; + ok: Scalars['Boolean']['output']; +}; + +export type UpdateLetterInput = { + book?: InputMaybe; + categories?: InputMaybe>; + categorisations?: InputMaybe>; + chapter?: InputMaybe; + description?: InputMaybe; + designators?: InputMaybe>; + id: Scalars['ID']['input']; + name?: InputMaybe; + page?: InputMaybe; +}; + +export type UpdateLetterMutation = { + __typename?: 'UpdateLetterMutation'; + errors: Array; + ok: Scalars['Boolean']['output']; +}; + export type UpdateOrCreateSourceMutation = { __typename?: 'UpdateOrCreateSourceMutation'; errors?: Maybe>>; @@ -1041,19 +1164,85 @@ export type DataEntryCreateEpisodeMutationVariables = Exact<{ export type DataEntryCreateEpisodeMutation = { __typename?: 'Mutation', createEpisode?: { __typename?: 'CreateEpisodeMutation', episode?: { __typename?: 'EpisodeType', id: string } | null, errors: Array<{ __typename?: 'LettercraftErrorType', field: string, messages: Array }> } | null }; -export type DataEntryGiftQueryVariables = Exact<{ +export type DataEntryGiftCategoriesQueryVariables = Exact<{ + id: Scalars['ID']['input']; +}>; + + +export type DataEntryGiftCategoriesQuery = { __typename?: 'Query', giftDescription?: { __typename?: 'GiftDescriptionType', id: string, categorisations: Array<{ __typename?: 'GiftDescriptionCategoryType', id: string, sourceMention?: LetterGiftDescriptionCategorySourceMentionChoices | null, note: string, certainty: LetterGiftDescriptionCategoryCertaintyChoices, category: { __typename?: 'GiftCategoryType', id: string, name: string } }> } | null }; + +export type DataEntryAllGiftCategoriesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type DataEntryAllGiftCategoriesQuery = { __typename?: 'Query', letterCategories: Array<{ __typename?: 'LetterCategoryType', id: string, label: string, description: string }> }; + +export type DataEntryGiftIdentificationQueryVariables = Exact<{ + id: Scalars['ID']['input']; +}>; + + +export type DataEntryGiftIdentificationQuery = { __typename?: 'Query', giftDescription?: { __typename?: 'GiftDescriptionType', id: string, name: string, description: string } | null }; + +export type DataEntryGiftSourceTextQueryVariables = Exact<{ + id: Scalars['ID']['input']; +}>; + + +export type DataEntryGiftSourceTextQuery = { __typename?: 'Query', giftDescription?: { __typename?: 'GiftDescriptionType', id: string, designators: Array, book: string, chapter: string, page: string } | null }; + +export type DataEntryGiftFormQueryVariables = Exact<{ + id: Scalars['ID']['input']; +}>; + + +export type DataEntryGiftFormQuery = { __typename?: 'Query', giftDescription?: { __typename?: 'GiftDescriptionType', id: string, name: string, description: string, source: { __typename?: 'SourceType', id: string, name: string } } | null }; + +export type DataEntryUpdateGiftMutationVariables = Exact<{ + giftData: UpdateGiftInput; +}>; + + +export type DataEntryUpdateGiftMutation = { __typename?: 'Mutation', updateGift?: { __typename?: 'UpdateGiftMutation', ok: boolean, errors: Array<{ __typename?: 'LettercraftErrorType', field: string, messages: Array }> } | null }; + +export type DataEntryLetterCategoriesQueryVariables = Exact<{ + id: Scalars['ID']['input']; +}>; + + +export type DataEntryLetterCategoriesQuery = { __typename?: 'Query', letterDescription?: { __typename?: 'LetterDescriptionType', id: string, categorisations: Array<{ __typename?: 'LetterDescriptionCategoryType', id: string, sourceMention?: LetterLetterDescriptionCategorySourceMentionChoices | null, note: string, certainty: LetterLetterDescriptionCategoryCertaintyChoices, category: { __typename?: 'LetterCategoryType', id: string, label: string } }> } | null }; + +export type DataEntryAllLetterCategoriesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type DataEntryAllLetterCategoriesQuery = { __typename?: 'Query', letterCategories: Array<{ __typename?: 'LetterCategoryType', id: string, label: string, description: string }> }; + +export type DataEntryLetterIdentificationQueryVariables = Exact<{ + id: Scalars['ID']['input']; +}>; + + +export type DataEntryLetterIdentificationQuery = { __typename?: 'Query', letterDescription?: { __typename?: 'LetterDescriptionType', id: string, name: string, description: string } | null }; + +export type DataEntryLetterSourceTextQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type DataEntryGiftQuery = { __typename?: 'Query', giftDescription?: { __typename?: 'GiftDescriptionType', id: string, name: string, description: string, source: { __typename?: 'SourceType', id: string, name: string } } | null }; +export type DataEntryLetterSourceTextQuery = { __typename?: 'Query', letterDescription?: { __typename?: 'LetterDescriptionType', id: string, designators: Array, book: string, chapter: string, page: string } | null }; -export type DataEntryLetterQueryVariables = Exact<{ +export type DataEntryLetterFormQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type DataEntryLetterQuery = { __typename?: 'Query', letterDescription?: { __typename?: 'LetterDescriptionType', id: string, name: string, description: string, source: { __typename?: 'SourceType', id: string, name: string } } | null }; +export type DataEntryLetterFormQuery = { __typename?: 'Query', letterDescription?: { __typename?: 'LetterDescriptionType', id: string, name: string, description: string, source: { __typename?: 'SourceType', id: string, name: string } } | null }; + +export type DataEntryUpdateLetterMutationVariables = Exact<{ + letterData: UpdateLetterInput; +}>; + + +export type DataEntryUpdateLetterMutation = { __typename?: 'Mutation', updateLetter?: { __typename?: 'UpdateLetterMutation', ok: boolean, errors: Array<{ __typename?: 'LettercraftErrorType', field: string, messages: Array }> } | null }; export type DataEntrySpaceDescriptionQueryVariables = Exact<{ id: Scalars['ID']['input']; @@ -1362,8 +1551,98 @@ export const DataEntryCreateEpisodeDocument = gql` super(apollo); } } -export const DataEntryGiftDocument = gql` - query DataEntryGift($id: ID!) { +export const DataEntryGiftCategoriesDocument = gql` + query DataEntryGiftCategories($id: ID!) { + giftDescription(id: $id) { + id + categorisations { + id + sourceMention + note + certainty + category { + id + name + } + } + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryGiftCategoriesGQL extends Apollo.Query { + override document = DataEntryGiftCategoriesDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryAllGiftCategoriesDocument = gql` + query DataEntryAllGiftCategories { + letterCategories { + id + label + description + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryAllGiftCategoriesGQL extends Apollo.Query { + override document = DataEntryAllGiftCategoriesDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryGiftIdentificationDocument = gql` + query DataEntryGiftIdentification($id: ID!) { + giftDescription(id: $id) { + id + name + description + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryGiftIdentificationGQL extends Apollo.Query { + override document = DataEntryGiftIdentificationDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryGiftSourceTextDocument = gql` + query DataEntryGiftSourceText($id: ID!) { + giftDescription(id: $id) { + id + designators + book + chapter + page + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryGiftSourceTextGQL extends Apollo.Query { + override document = DataEntryGiftSourceTextDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryGiftFormDocument = gql` + query DataEntryGiftForm($id: ID!) { giftDescription(id: $id) { id name @@ -1379,15 +1658,127 @@ export const DataEntryGiftDocument = gql` @Injectable({ providedIn: 'root' }) - export class DataEntryGiftGQL extends Apollo.Query { - override document = DataEntryGiftDocument; + export class DataEntryGiftFormGQL extends Apollo.Query { + override document = DataEntryGiftFormDocument; constructor(apollo: Apollo.Apollo) { super(apollo); } } -export const DataEntryLetterDocument = gql` - query DataEntryLetter($id: ID!) { +export const DataEntryUpdateGiftDocument = gql` + mutation DataEntryUpdateGift($giftData: UpdateGiftInput!) { + updateGift(giftData: $giftData) { + ok + errors { + field + messages + } + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryUpdateGiftGQL extends Apollo.Mutation { + override document = DataEntryUpdateGiftDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryLetterCategoriesDocument = gql` + query DataEntryLetterCategories($id: ID!) { + letterDescription(id: $id) { + id + categorisations { + id + sourceMention + note + certainty + category { + id + label + } + } + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryLetterCategoriesGQL extends Apollo.Query { + override document = DataEntryLetterCategoriesDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryAllLetterCategoriesDocument = gql` + query DataEntryAllLetterCategories { + letterCategories { + id + label + description + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryAllLetterCategoriesGQL extends Apollo.Query { + override document = DataEntryAllLetterCategoriesDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryLetterIdentificationDocument = gql` + query DataEntryLetterIdentification($id: ID!) { + letterDescription(id: $id) { + id + name + description + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryLetterIdentificationGQL extends Apollo.Query { + override document = DataEntryLetterIdentificationDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryLetterSourceTextDocument = gql` + query DataEntryLetterSourceText($id: ID!) { + letterDescription(id: $id) { + id + designators + book + chapter + page + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryLetterSourceTextGQL extends Apollo.Query { + override document = DataEntryLetterSourceTextDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryLetterFormDocument = gql` + query DataEntryLetterForm($id: ID!) { letterDescription(id: $id) { id name @@ -1403,8 +1794,30 @@ export const DataEntryLetterDocument = gql` @Injectable({ providedIn: 'root' }) - export class DataEntryLetterGQL extends Apollo.Query { - override document = DataEntryLetterDocument; + export class DataEntryLetterFormGQL extends Apollo.Query { + override document = DataEntryLetterFormDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } + } +export const DataEntryUpdateLetterDocument = gql` + mutation DataEntryUpdateLetter($letterData: UpdateLetterInput!) { + updateLetter(letterData: $letterData) { + ok + errors { + field + messages + } + } +} + `; + + @Injectable({ + providedIn: 'root' + }) + export class DataEntryUpdateLetterGQL extends Apollo.Mutation { + override document = DataEntryUpdateLetterDocument; constructor(apollo: Apollo.Apollo) { super(apollo); diff --git a/frontend/generated/schema.graphql b/frontend/generated/schema.graphql index c432050d..85b8da4c 100644 --- a/frontend/generated/schema.graphql +++ b/frontend/generated/schema.graphql @@ -99,6 +99,26 @@ type CreateEpisodeMutation { errors: [LettercraftErrorType!]! } +input CreateGiftInput { + name: String! + source: ID! +} + +type CreateGiftMutation { + errors: [LettercraftErrorType!]! + gift: GiftDescriptionType +} + +input CreateLetterInput { + name: String! + source: ID! +} + +type CreateLetterMutation { + errors: [LettercraftErrorType!]! + letter: LetterDescriptionType +} + input CreatePersonReferenceInput { description: ID! person: ID! @@ -119,6 +139,16 @@ type DeleteEpisodeMutation { ok: Boolean! } +type DeleteGiftMutation { + errors: [LettercraftErrorType!]! + ok: Boolean! +} + +type DeleteLetterMutation { + errors: [LettercraftErrorType!]! + ok: Boolean! +} + type DeletePersonReferenceMutation { errors: [LettercraftErrorType!]! ok: Boolean! @@ -197,6 +227,14 @@ enum Gender { UNKNOWN } +input GiftCategorisationInput { + category: ID! + certainty: String + id: ID + note: String + sourceMention: SourceMention +} + type GiftCategoryType { """Longer description to help identify this object""" description: String! @@ -271,6 +309,14 @@ type HistoricalPersonType { name: String! } +input LetterCategorisationInput { + category: ID! + certainty: String + id: ID + note: String + sourceMention: SourceMention +} + type LetterCategoryType { description: String! id: ID! @@ -395,12 +441,18 @@ type LettercraftErrorType { type Mutation { createAgent(agentData: CreateAgentInput!): CreateAgentMutation createEpisode(episodeData: CreateEpisodeInput!): CreateEpisodeMutation + createGift(giftData: CreateGiftInput!): CreateGiftMutation + createLetter(letterData: CreateLetterInput!): CreateLetterMutation createPersonReference(referenceData: CreatePersonReferenceInput!): CreatePersonReferenceMutation deleteAgent(id: ID!): DeleteAgentMutation deleteEpisode(id: ID!): DeleteEpisodeMutation + deleteGift(id: ID!): DeleteGiftMutation + deleteLetter(id: ID!): DeleteLetterMutation deletePersonReference(id: ID!): DeletePersonReferenceMutation updateAgent(agentData: UpdateAgentInput!): UpdateAgentMutation updateEpisode(episodeData: UpdateEpisodeInput!): UpdateEpisodeMutation + updateGift(giftData: UpdateGiftInput!): UpdateGiftMutation + updateLetter(letterData: UpdateLetterInput!): UpdateLetterMutation updateOrCreateSource(sourceData: UpdateCreateSourceInput!): UpdateOrCreateSourceMutation updatePersonReference(referenceData: UpdatePersonReferenceInput!): UpdatePersonReferenceMutation } @@ -577,6 +629,7 @@ type Query { episodes(sourceId: ID): [EpisodeType!]! giftDescription(id: ID!): GiftDescriptionType giftDescriptions(episodeId: ID, sourceId: ID): [GiftDescriptionType!]! + letterCategories: [LetterCategoryType!]! letterDescription(id: ID!): LetterDescriptionType letterDescriptions(episodeId: ID, sourceId: ID): [LetterDescriptionType!]! source(id: ID!): SourceType! @@ -946,6 +999,40 @@ type UpdateEpisodeMutation { ok: Boolean! } +input UpdateGiftInput { + book: String + categories: [ID!] + categorisations: [GiftCategorisationInput!] + chapter: String + description: String + designators: [String!] + id: ID! + name: String + page: String +} + +type UpdateGiftMutation { + errors: [LettercraftErrorType!]! + ok: Boolean! +} + +input UpdateLetterInput { + book: String + categories: [ID!] + categorisations: [LetterCategorisationInput!] + chapter: String + description: String + designators: [String!] + id: ID! + name: String + page: String +} + +type UpdateLetterMutation { + errors: [LettercraftErrorType!]! + ok: Boolean! +} + type UpdateOrCreateSourceMutation { errors: [String] source: SourceType diff --git a/frontend/src/app/data-entry/episode-form/episode-source-text-form/episode-source-text-mention-form.graphql b/frontend/src/app/data-entry/episode-form/episode-source-text-form/episode-source-text-form.graphql similarity index 100% rename from frontend/src/app/data-entry/episode-form/episode-source-text-form/episode-source-text-mention-form.graphql rename to frontend/src/app/data-entry/episode-form/episode-source-text-form/episode-source-text-form.graphql diff --git a/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.html b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.html new file mode 100644 index 00000000..ccafaf90 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.html @@ -0,0 +1,6 @@ +
+
+

Select one or more categories that apply to this letter

+ Coming soon! +
+
diff --git a/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.scss b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.spec.ts b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.spec.ts new file mode 100644 index 00000000..e59eb802 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { GiftCategoriesFormComponent } from "./gift-categories-form.component"; +import { SharedTestingModule } from "@shared/shared-testing.module"; + +describe("GiftCategoriesFormComponent", () => { + let component: GiftCategoriesFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [GiftCategoriesFormComponent], + imports: [SharedTestingModule] + }); + fixture = TestBed.createComponent(GiftCategoriesFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.ts b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.ts new file mode 100644 index 00000000..60061838 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.component.ts @@ -0,0 +1,54 @@ +import { Component, DestroyRef } from "@angular/core"; +import { FormGroup, FormControl } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { ToastService } from "@services/toast.service"; +import { + DataEntryGiftCategoriesGQL, + DataEntryAllGiftCategoriesGQL, + DataEntryUpdateGiftGQL, +} from "generated/graphql"; +import { map, share, switchMap, Observable } from "rxjs"; +import { MultiselectOption } from "../../shared/multiselect/multiselect.component"; + +@Component({ + selector: "lc-gift-categories-form", + templateUrl: "./gift-categories-form.component.html", + styleUrls: ["./gift-categories-form.component.scss"], +}) +export class GiftCategoriesFormComponent { + public id$ = this.route.params.pipe( + map((params) => params["id"]), + share() + ); + + public letter$ = this.id$.pipe( + switchMap((id) => this.giftQuery.watch({ id }).valueChanges), + map((result) => result.data.giftDescription) + ); + + public form = new FormGroup({ + categorisations: new FormControl([], { + nonNullable: true, + }), + }); + + public giftCategories$: Observable = + this.giftCategoriesQuery.watch().valueChanges.pipe( + map((result) => result.data.letterCategories), + map((categories) => + categories.map((category) => ({ + value: category.id, + label: category.label, + })) + ) + ); + + constructor( + private destroyRef: DestroyRef, + private route: ActivatedRoute, + private toastService: ToastService, + private giftQuery: DataEntryGiftCategoriesGQL, + private giftCategoriesQuery: DataEntryAllGiftCategoriesGQL, + private giftMutation: DataEntryUpdateGiftGQL + ) {} +} diff --git a/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.graphql b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.graphql new file mode 100644 index 00000000..5e3e5760 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-categories-form/gift-categories-form.graphql @@ -0,0 +1,23 @@ +query DataEntryGiftCategories($id: ID!) { + giftDescription(id: $id) { + id + categorisations { + id + sourceMention + note + certainty + category { + id + name + } + } + } +} + +query DataEntryAllGiftCategories { + letterCategories { + id + label + description + } +} diff --git a/frontend/src/app/data-entry/gift-form/gift-form.component.html b/frontend/src/app/data-entry/gift-form/gift-form.component.html index a5d52015..68979b07 100644 --- a/frontend/src/app/data-entry/gift-form/gift-form.component.html +++ b/frontend/src/app/data-entry/gift-form/gift-form.component.html @@ -1,21 +1,30 @@ - - + - -

- - {{giftDescription.name}} - - ({{giftDescription.source.name}}) - -

+ +

+ + {{ gift.name }} + + ({{ gift.source.name }}) + +

-

- {{giftDescription.description}} -

- -
- This form is not implemented yet! -
-
+

+ {{ gift.description }} +

+ +

Identification

+ + +

Source text

+ + +

Categories

+ + + +
+ Loading... +
+
diff --git a/frontend/src/app/data-entry/gift-form/gift-form.component.spec.ts b/frontend/src/app/data-entry/gift-form/gift-form.component.spec.ts index 1f8f8d1b..37b564f5 100644 --- a/frontend/src/app/data-entry/gift-form/gift-form.component.spec.ts +++ b/frontend/src/app/data-entry/gift-form/gift-form.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { GiftFormComponent } from './gift-form.component'; import { SharedTestingModule } from '@shared/shared-testing.module'; +import { GiftFormModule } from './gift-form.module'; describe('GiftFormComponent', () => { let component: GiftFormComponent; @@ -10,7 +11,7 @@ describe('GiftFormComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [GiftFormComponent], - imports: [SharedTestingModule], + imports: [GiftFormModule, SharedTestingModule], }); fixture = TestBed.createComponent(GiftFormComponent); component = fixture.componentInstance; diff --git a/frontend/src/app/data-entry/gift-form/gift-form.component.ts b/frontend/src/app/data-entry/gift-form/gift-form.component.ts index eb24da84..bf090ec8 100644 --- a/frontend/src/app/data-entry/gift-form/gift-form.component.ts +++ b/frontend/src/app/data-entry/gift-form/gift-form.component.ts @@ -1,51 +1,55 @@ -import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Breadcrumb } from '@shared/breadcrumb/breadcrumb.component'; -import { dataIcons } from '@shared/icons'; -import { DataEntryGiftGQL, DataEntryGiftQuery } from 'generated/graphql'; -import { map, Observable, switchMap } from 'rxjs'; +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { actionIcons, dataIcons } from "@shared/icons"; +import { DataEntryGiftFormGQL } from "generated/graphql"; +import { filter, map, share, switchMap } from "rxjs"; @Component({ - selector: 'lc-gift-form', - templateUrl: './gift-form.component.html', - styleUrls: ['./gift-form.component.scss'] + selector: "lc-gift-form", + templateUrl: "./gift-form.component.html", + styleUrls: ["./gift-form.component.scss"], }) export class GiftFormComponent { - id$: Observable; - data$: Observable; + private id$ = this.route.params.pipe(map((params) => params["id"])); - dataIcons = dataIcons; + public gift$ = this.id$.pipe( + switchMap((id) => this.giftQuery.watch({ id }).valueChanges), + map((result) => result.data.giftDescription), + share() + ); - constructor(private route: ActivatedRoute, private giftQuery: DataEntryGiftGQL) { - this.id$ = this.route.params.pipe( - map(params => params['id']), - ); - this.data$ = this.id$.pipe( - switchMap(id => this.giftQuery.watch({ id }).valueChanges), - map(result => result.data), - ); - } - - getBreadcrumbs(data: DataEntryGiftQuery): Breadcrumb[] { - if (data.giftDescription) { + public breadcrumbs$ = this.gift$.pipe( + filter((gift) => !!gift), + map((gift) => { + if (!gift) { + return []; + } return [ - { link: '/', label: 'Lettercraft' }, - { link: '/data-entry', label: 'Data entry' }, { - link: `/data-entry/sources/${data.giftDescription.source.id}`, - label: data.giftDescription.source.name + label: "Lettercraft", + link: "/", + }, + { + label: "Data entry", + link: "/data-entry", + }, + { + label: gift.source.name, + link: `/data-entry/sources/${gift.source.id}`, }, { - link: `/data-entry/gifts/${data.giftDescription.id}`, - label: data.giftDescription.name + label: gift.name, + link: `/data-entry/gifts/${gift.id}`, }, ]; - } else { - return [ - { link: '/', label: 'Lettercraft' }, - { link: '/data-entry', label: 'Data entry' }, - { link: '', label: 'Gift not found' } - ] - } - } + }) + ); + + public dataIcons = dataIcons; + public actionIcons = actionIcons; + + constructor( + private route: ActivatedRoute, + private giftQuery: DataEntryGiftFormGQL + ) {} } diff --git a/frontend/src/app/data-entry/gift-form/gift-form.module.ts b/frontend/src/app/data-entry/gift-form/gift-form.module.ts index e617f982..784a8128 100644 --- a/frontend/src/app/data-entry/gift-form/gift-form.module.ts +++ b/frontend/src/app/data-entry/gift-form/gift-form.module.ts @@ -1,15 +1,22 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '@shared/shared.module'; import { GiftFormComponent } from './gift-form.component'; +import { GiftIdentificationFormComponent } from './gift-identification-form/gift-identification-form.component'; +import { GiftSourceTextFormComponent } from './gift-source-text-form/gift-source-text-form.component'; +import { GiftCategoriesFormComponent } from './gift-categories-form/gift-categories-form.component'; +import { DataEntrySharedModule } from '../shared/data-entry-shared.module'; @NgModule({ declarations: [ GiftFormComponent, + GiftIdentificationFormComponent, + GiftSourceTextFormComponent, + GiftCategoriesFormComponent, ], imports: [ - SharedModule + SharedModule, DataEntrySharedModule ], exports: [ GiftFormComponent, diff --git a/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.html b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.html new file mode 100644 index 00000000..139ec834 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.html @@ -0,0 +1,31 @@ +
+
+ +

+ This is a name to identify the gift in the overview of the source. + For example: "golden cup", "hair shirt". +

+ +

This field is required.

+
+ +
+
Description
+

+ Add a description to help identify this gift in the database. +

+ +
+
diff --git a/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.scss b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.spec.ts b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.spec.ts new file mode 100644 index 00000000..e1bc216c --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { GiftIdentificationFormComponent } from "./gift-identification-form.component"; +import { SharedTestingModule } from "@shared/shared-testing.module"; + +describe("GiftIdentificationFormComponent", () => { + let component: GiftIdentificationFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [GiftIdentificationFormComponent], + imports: [SharedTestingModule] + }); + fixture = TestBed.createComponent(GiftIdentificationFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.ts b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.ts new file mode 100644 index 00000000..886ed7c3 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.component.ts @@ -0,0 +1,127 @@ +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormGroup, FormControl, Validators } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { ApolloCache } from "@apollo/client/core"; +import { ToastService } from "@services/toast.service"; +import { MutationResult } from "apollo-angular"; +import { + DataEntryGiftIdentificationGQL, + DataEntryUpdateGiftGQL, + DataEntryUpdateGiftMutation, +} from "generated/graphql"; +import { + map, + switchMap, + shareReplay, + filter, + debounceTime, + withLatestFrom, + Observable, +} from "rxjs"; + +interface GiftIdentification { + name: string; + description: string; +} + +type GiftIdentificationForm = { + [key in keyof GiftIdentification]: FormControl; +}; + +@Component({ + selector: "lc-gift-identification-form", + templateUrl: "./gift-identification-form.component.html", + styleUrls: ["./gift-identification-form.component.scss"], +}) +export class GiftIdentificationFormComponent implements OnInit { + public id$ = this.route.params.pipe(map((params) => params["id"])); + + public gift$ = this.id$.pipe( + switchMap((id) => this.giftQuery.watch({ id }).valueChanges), + map((result) => result.data.giftDescription), + shareReplay(1) + ); + + public form = new FormGroup({ + name: new FormControl("", { + validators: [Validators.required], + nonNullable: true, + }), + description: new FormControl("", { + nonNullable: true, + }), + }); + + constructor( + private destroyRef: DestroyRef, + private route: ActivatedRoute, + private toastService: ToastService, + private giftQuery: DataEntryGiftIdentificationGQL, + private giftMutation: DataEntryUpdateGiftGQL + ) {} + + ngOnInit(): void { + this.gift$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((gift) => { + if (!gift) { + return; + } + this.form.patchValue(gift, { + emitEvent: false, + onlySelf: true, + }); + }); + + this.gift$ + .pipe( + switchMap(() => + this.form.valueChanges.pipe( + map(() => this.form.getRawValue()), + filter(() => this.form.valid), + debounceTime(300), + withLatestFrom(this.id$), + switchMap(([gift, id]) => + this.performMutation(gift, id) + ) + ) + ), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((result) => { + const errors = result.data?.updateGift?.errors; + if (errors && errors.length > 0) { + this.toastService.show({ + body: errors.map((e) => e.messages).join("\n"), + type: "danger", + header: "Update failed", + }); + } + }); + } + + private performMutation( + gift: GiftIdentification, + id: string + ): Observable> { + return this.giftMutation.mutate( + { + giftData: { + ...gift, + id, + }, + }, + { + update: (cache) => this.updateCache(cache, id), + } + ); + } + + private updateCache(cache: ApolloCache, id: string): void { + cache.evict({ + id: `GiftDescriptionType:${id}`, + }); + cache.gc(); + } +} diff --git a/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.graphql b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.graphql new file mode 100644 index 00000000..0fad590f --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-identification-form/gift-identification-form.graphql @@ -0,0 +1,7 @@ +query DataEntryGiftIdentification($id: ID!) { + giftDescription(id: $id) { + id + name + description + } +} diff --git a/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.html b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.html new file mode 100644 index 00000000..819d7cc9 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.html @@ -0,0 +1,37 @@ +
+ + +

Location

+

+ Describe the location of this gift in the source text. +

+
+
+ + +
+
+ + +
+
+ + +
+
+ diff --git a/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.scss b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.spec.ts b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.spec.ts new file mode 100644 index 00000000..357f0177 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { GiftSourceTextFormComponent } from "./gift-source-text-form.component"; +import { SharedTestingModule } from "@shared/shared-testing.module"; + +describe("GiftSourceTextFormComponent", () => { + let component: GiftSourceTextFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [GiftSourceTextFormComponent], + imports: [SharedTestingModule] + }); + fixture = TestBed.createComponent(GiftSourceTextFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.ts b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.ts new file mode 100644 index 00000000..484d793e --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.component.ts @@ -0,0 +1,100 @@ +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormGroup, FormControl } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { ToastService } from "@services/toast.service"; +import { + DataEntryGiftSourceTextGQL, + DataEntryUpdateGiftGQL, +} from "generated/graphql"; +import { + map, + switchMap, + shareReplay, + filter, + debounceTime, + withLatestFrom, +} from "rxjs"; + +@Component({ + selector: "lc-gift-source-text-form", + templateUrl: "./gift-source-text-form.component.html", + styleUrls: ["./gift-source-text-form.component.scss"], +}) +export class GiftSourceTextFormComponent implements OnInit { + private id$ = this.route.params.pipe(map((params) => params["id"])); + + public gift$ = this.id$.pipe( + switchMap((id) => this.giftQuery.watch({ id }).valueChanges), + map((result) => result.data.giftDescription), + shareReplay(1) + ); + + public form = new FormGroup({ + designators: new FormControl([], { + nonNullable: true, + }), + book: new FormControl("", { + nonNullable: true, + }), + chapter: new FormControl("", { + nonNullable: true, + }), + page: new FormControl("", { + nonNullable: true, + }), + }); + + constructor( + private destroyRef: DestroyRef, + private route: ActivatedRoute, + private toastService: ToastService, + private giftQuery: DataEntryGiftSourceTextGQL, + private giftMutation: DataEntryUpdateGiftGQL + ) {} + + ngOnInit(): void { + this.gift$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((gift) => { + if (!gift) { + return; + } + this.form.patchValue(gift, { + emitEvent: false, + onlySelf: true, + }); + }); + + this.gift$ + .pipe( + switchMap(() => + this.form.valueChanges.pipe( + map(() => this.form.getRawValue()), + filter(() => this.form.valid), + debounceTime(300), + withLatestFrom(this.id$), + switchMap(([gift, id]) => + this.giftMutation.mutate({ + giftData: { + ...gift, + id, + }, + }) + ) + ) + ), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((result) => { + const errors = result.data?.updateGift?.errors; + if (errors && errors.length > 0) { + this.toastService.show({ + body: errors.map((error) => error.messages).join("\n"), + type: "danger", + header: "Update failed", + }); + } + }); + } +} diff --git a/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.graphql b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.graphql new file mode 100644 index 00000000..cba21288 --- /dev/null +++ b/frontend/src/app/data-entry/gift-form/gift-source-text-form/gift-source-text-form.graphql @@ -0,0 +1,9 @@ +query DataEntryGiftSourceText($id: ID!) { + giftDescription(id: $id) { + id + designators + book + chapter + page + } +} diff --git a/frontend/src/app/data-entry/gift-form/gift.graphql b/frontend/src/app/data-entry/gift-form/gift.graphql index ee855d60..48dda6c9 100644 --- a/frontend/src/app/data-entry/gift-form/gift.graphql +++ b/frontend/src/app/data-entry/gift-form/gift.graphql @@ -1,11 +1,21 @@ -query DataEntryGift($id: ID!) { - giftDescription(id: $id) { - id - name - description - source { - id - name +query DataEntryGiftForm($id: ID!) { + giftDescription(id: $id) { + id + name + description + source { + id + name + } + } +} + +mutation DataEntryUpdateGift($giftData: UpdateGiftInput!) { + updateGift(giftData: $giftData) { + ok + errors { + field + messages + } } - } } diff --git a/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.html b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.html new file mode 100644 index 00000000..e50ad001 --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.html @@ -0,0 +1,6 @@ +
+
+

Select one or more categories that apply to this letter

+ Coming soon! +
+
diff --git a/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.scss b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.spec.ts b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.spec.ts new file mode 100644 index 00000000..103285a1 --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { LetterCategoriesFormComponent } from "./letter-categories-form.component"; +import { SharedTestingModule } from "@shared/shared-testing.module"; + +describe("LetterCategoriesFormComponent", () => { + let component: LetterCategoriesFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [LetterCategoriesFormComponent], + imports: [SharedTestingModule] + }); + fixture = TestBed.createComponent(LetterCategoriesFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.ts b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.ts new file mode 100644 index 00000000..a38b0fb5 --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.component.ts @@ -0,0 +1,54 @@ +import { Component, DestroyRef } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { ToastService } from "@services/toast.service"; +import { + DataEntryAllLetterCategoriesGQL, + DataEntryLetterCategoriesGQL, + DataEntryUpdateLetterGQL, +} from "generated/graphql"; +import { map, Observable, share, switchMap } from "rxjs"; +import { MultiselectOption } from "../../shared/multiselect/multiselect.component"; + +@Component({ + selector: "lc-letter-categories-form", + templateUrl: "./letter-categories-form.component.html", + styleUrls: ["./letter-categories-form.component.scss"], +}) +export class LetterCategoriesFormComponent { + public id$ = this.route.params.pipe( + map((params) => params["id"]), + share() + ); + + public letter$ = this.id$.pipe( + switchMap((id) => this.letterQuery.watch({ id }).valueChanges), + map((result) => result.data.letterDescription) + ); + + public form = new FormGroup({ + categorisations: new FormControl([], { + nonNullable: true, + }), + }); + + public letterCategories$: Observable = + this.letterCategoriesQuery.watch().valueChanges.pipe( + map((result) => result.data.letterCategories), + map((categories) => + categories.map((category) => ({ + value: category.id, + label: category.label, + })) + ) + ); + + constructor( + private destroyRef: DestroyRef, + private route: ActivatedRoute, + private toastService: ToastService, + private letterQuery: DataEntryLetterCategoriesGQL, + private letterCategoriesQuery: DataEntryAllLetterCategoriesGQL, + private letterMutation: DataEntryUpdateLetterGQL + ) {} +} diff --git a/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.graphql b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.graphql new file mode 100644 index 00000000..d5a8f05f --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-categories-form/letter-categories-form.graphql @@ -0,0 +1,23 @@ +query DataEntryLetterCategories($id: ID!) { + letterDescription(id: $id) { + id + categorisations { + id + sourceMention + note + certainty + category { + id + label + } + } + } +} + +query DataEntryAllLetterCategories { + letterCategories { + id + label + description + } +} diff --git a/frontend/src/app/data-entry/letter-form/letter-form.component.html b/frontend/src/app/data-entry/letter-form/letter-form.component.html index bc91e625..23c5a774 100644 --- a/frontend/src/app/data-entry/letter-form/letter-form.component.html +++ b/frontend/src/app/data-entry/letter-form/letter-form.component.html @@ -1,21 +1,30 @@ - - + - -

- - {{letterDescription.name}} - - ({{letterDescription.source.name}}) - -

+ +

+ + {{ letter.name }} + + ({{ letter.source.name }}) + +

-

- {{letterDescription.description}} -

- -

- This form is not implemented yet! -

-
+

+ {{ letter.description }} +

+ +

Identification

+ + +

Source text

+ + +

Categories

+ + + +
+ Loading... +
+
diff --git a/frontend/src/app/data-entry/letter-form/letter-form.component.spec.ts b/frontend/src/app/data-entry/letter-form/letter-form.component.spec.ts index 5e1a8467..fd346a13 100644 --- a/frontend/src/app/data-entry/letter-form/letter-form.component.spec.ts +++ b/frontend/src/app/data-entry/letter-form/letter-form.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LetterFormComponent } from './letter-form.component'; import { SharedTestingModule } from '@shared/shared-testing.module'; +import { LetterFormModule } from './letter-form.module'; describe('LetterFormComponent', () => { let component: LetterFormComponent; @@ -10,7 +11,7 @@ describe('LetterFormComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [LetterFormComponent], - imports: [SharedTestingModule], + imports: [LetterFormModule, SharedTestingModule], }); fixture = TestBed.createComponent(LetterFormComponent); component = fixture.componentInstance; diff --git a/frontend/src/app/data-entry/letter-form/letter-form.component.ts b/frontend/src/app/data-entry/letter-form/letter-form.component.ts index 6083ae9e..4dc1dabf 100644 --- a/frontend/src/app/data-entry/letter-form/letter-form.component.ts +++ b/frontend/src/app/data-entry/letter-form/letter-form.component.ts @@ -1,52 +1,55 @@ -import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Breadcrumb } from '@shared/breadcrumb/breadcrumb.component'; -import { dataIcons } from '@shared/icons'; -import { DataEntryLetterGQL, DataEntryLetterQuery } from 'generated/graphql'; -import { map, Observable, switchMap } from 'rxjs'; +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { actionIcons, dataIcons } from "@shared/icons"; +import { DataEntryLetterFormGQL } from "generated/graphql"; +import { filter, map, share, switchMap } from "rxjs"; @Component({ - selector: 'lc-letter-form', - templateUrl: './letter-form.component.html', - styleUrls: ['./letter-form.component.scss'] + selector: "lc-letter-form", + templateUrl: "./letter-form.component.html", + styleUrls: ["./letter-form.component.scss"], }) export class LetterFormComponent { - id$: Observable; - data$: Observable; + private id$ = this.route.params.pipe(map((params) => params["id"])); - dataIcons = dataIcons; + public letter$ = this.id$.pipe( + switchMap((id) => this.letterQuery.watch({ id }).valueChanges), + map((result) => result.data.letterDescription), + share() + ); - constructor(private route: ActivatedRoute, private letterQuery: DataEntryLetterGQL) { - this.id$ = this.route.params.pipe( - map(params => params['id']), - ); - this.data$ = this.id$.pipe( - switchMap(id => this.letterQuery.watch({ id }).valueChanges), - map(result => result.data), - ); - } - - getBreadcrumbs(data: DataEntryLetterQuery): Breadcrumb[] { - if (data.letterDescription) { + public breadcrumbs$ = this.letter$.pipe( + filter((letter) => !!letter), + map((letter) => { + if (!letter) { + return []; + } return [ - { link: '/', label: 'Lettercraft' }, - { link: '/data-entry', label: 'Data entry' }, { - link: `/data-entry/sources/${data.letterDescription.source.id}`, - label: data.letterDescription.source.name + label: "Lettercraft", + link: "/", + }, + { + label: "Data entry", + link: "/data-entry", }, { - link: `/data-entry/letters/${data.letterDescription.id}`, - label: data.letterDescription.name + label: letter.source.name, + link: `/data-entry/sources/${letter.source.id}`, + }, + { + label: letter.name, + link: `/data-entry/letters/${letter.id}`, }, ]; - } else { - return [ - { link: '/', label: 'Lettercraft' }, - { link: '/data-entry', label: 'Data entry' }, - { link: '', label: 'Letter not found' } - ] - } - } + }) + ); + + public dataIcons = dataIcons; + public actionIcons = actionIcons; + constructor( + private route: ActivatedRoute, + private letterQuery: DataEntryLetterFormGQL + ) {} } diff --git a/frontend/src/app/data-entry/letter-form/letter-form.module.ts b/frontend/src/app/data-entry/letter-form/letter-form.module.ts index 7fd7b41e..f1fc91a9 100644 --- a/frontend/src/app/data-entry/letter-form/letter-form.module.ts +++ b/frontend/src/app/data-entry/letter-form/letter-form.module.ts @@ -1,18 +1,19 @@ -import { NgModule } from '@angular/core'; -import { SharedModule } from '@shared/shared.module'; -import { LetterFormComponent } from './letter-form.component'; - - +import { NgModule } from "@angular/core"; +import { SharedModule } from "@shared/shared.module"; +import { LetterFormComponent } from "./letter-form.component"; +import { LetterCategoriesFormComponent } from "./letter-categories-form/letter-categories-form.component"; +import { LetterSourceTextFormComponent } from "./letter-source-text-form/letter-source-text-form.component"; +import { LetterIdentificationFormComponent } from "./letter-identification-form/letter-identification-form.component"; +import { DataEntrySharedModule } from "../shared/data-entry-shared.module"; @NgModule({ declarations: [ LetterFormComponent, + LetterCategoriesFormComponent, + LetterSourceTextFormComponent, + LetterIdentificationFormComponent, ], - imports: [ - SharedModule, - ], - exports: [ - LetterFormComponent, - ] + imports: [SharedModule, DataEntrySharedModule], + exports: [LetterFormComponent], }) -export class LetterFormModule { } +export class LetterFormModule {} diff --git a/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.html b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.html new file mode 100644 index 00000000..37e8ba7e --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.html @@ -0,0 +1,29 @@ +
+
+ +

+ This is a name to identify the letter in the overview of the source. + For example: "letter from Radegund to Germanus", "Radegund's first + letter". +

+ +

This field is required.

+
+ +
+
Description
+

Add a description to help identify this letter in the database.

+ +
+
diff --git a/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.scss b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.spec.ts b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.spec.ts new file mode 100644 index 00000000..88253157 --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { LetterIdentificationFormComponent } from "./letter-identification-form.component"; +import { SharedTestingModule } from "@shared/shared-testing.module"; + +describe("LetterIdentificationFormComponent", () => { + let component: LetterIdentificationFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [LetterIdentificationFormComponent], + imports: [SharedTestingModule] + }); + fixture = TestBed.createComponent(LetterIdentificationFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.ts b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.ts new file mode 100644 index 00000000..965ef88a --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.component.ts @@ -0,0 +1,105 @@ +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { ToastService } from "@services/toast.service"; +import { + DataEntryLetterIdentificationGQL, + DataEntryUpdateLetterGQL, +} from "generated/graphql"; +import { + debounceTime, + filter, + map, + shareReplay, + switchMap, + withLatestFrom, +} from "rxjs/operators"; + +@Component({ + selector: "lc-letter-identification-form", + templateUrl: "./letter-identification-form.component.html", + styleUrls: ["./letter-identification-form.component.scss"], +}) +export class LetterIdentificationFormComponent implements OnInit { + public id$ = this.route.params.pipe(map((params) => params["id"])); + + public letter$ = this.id$.pipe( + switchMap((id) => this.letterQuery.watch({ id }).valueChanges), + map((result) => result.data.letterDescription), + shareReplay(1) + ); + + public form = new FormGroup({ + name: new FormControl("", { + validators: [Validators.required], + nonNullable: true, + }), + description: new FormControl("", { + nonNullable: true, + }), + }); + + constructor( + private destroyRef: DestroyRef, + private route: ActivatedRoute, + private toastService: ToastService, + private letterQuery: DataEntryLetterIdentificationGQL, + private letterMutation: DataEntryUpdateLetterGQL + ) {} + + ngOnInit(): void { + this.letter$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((letter) => { + if (!letter) { + return; + } + this.form.patchValue(letter, { + emitEvent: false, + onlySelf: true, + }); + }); + + this.letter$ + .pipe( + switchMap(() => + this.form.valueChanges.pipe( + map(() => this.form.getRawValue()), + filter(() => this.form.valid), + debounceTime(300), + withLatestFrom(this.id$), + switchMap(([letter, id]) => + this.letterMutation.mutate( + { + letterData: { + ...letter, + id, + }, + }, + { + update: (cache) => { + cache.evict({ + id: `LetterDescriptionType:${id}`, + }); + cache.gc(); + }, + } + ) + ) + ) + ), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((result) => { + const errors = result.data?.updateLetter?.errors; + if (errors && errors.length > 0) { + this.toastService.show({ + body: errors.map((e) => e.messages).join("\n"), + type: "danger", + header: "Update failed", + }); + } + }); + } +} diff --git a/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.graphql b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.graphql new file mode 100644 index 00000000..9cfd6c59 --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-identification-form/letter-identification-form.graphql @@ -0,0 +1,7 @@ +query DataEntryLetterIdentification($id: ID!) { + letterDescription(id: $id) { + id + name + description + } +} diff --git a/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.html b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.html new file mode 100644 index 00000000..6708c3ff --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.html @@ -0,0 +1,37 @@ +
+ + +

Location

+

+ Describe the location of this letter in the source text. +

+
+
+ + +
+
+ + +
+
+ + +
+
+ diff --git a/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.scss b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.spec.ts b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.spec.ts new file mode 100644 index 00000000..4a740170 --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { LetterSourceTextFormComponent } from "./letter-source-text-form.component"; +import { SharedTestingModule } from "@shared/shared-testing.module"; + +describe("LetterSourceTextFormComponent", () => { + let component: LetterSourceTextFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [LetterSourceTextFormComponent], + imports: [SharedTestingModule] + }); + fixture = TestBed.createComponent(LetterSourceTextFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.ts b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.ts new file mode 100644 index 00000000..9daaa4ab --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.component.ts @@ -0,0 +1,100 @@ +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl, FormGroup } from "@angular/forms"; +import { ActivatedRoute } from "@angular/router"; +import { ToastService } from "@services/toast.service"; +import { + DataEntryLetterSourceTextGQL, + DataEntryUpdateLetterGQL, +} from "generated/graphql"; +import { + debounceTime, + filter, + map, + shareReplay, + switchMap, + withLatestFrom, +} from "rxjs"; + +@Component({ + selector: "lc-letter-source-text-form", + templateUrl: "./letter-source-text-form.component.html", + styleUrls: ["./letter-source-text-form.component.scss"], +}) +export class LetterSourceTextFormComponent implements OnInit { + private id$ = this.route.params.pipe(map((params) => params["id"])); + + public letter$ = this.id$.pipe( + switchMap((id) => this.letterQuery.watch({ id }).valueChanges), + map((result) => result.data.letterDescription), + shareReplay(1) + ); + + public form = new FormGroup({ + designators: new FormControl([], { + nonNullable: true, + }), + book: new FormControl("", { + nonNullable: true, + }), + chapter: new FormControl("", { + nonNullable: true, + }), + page: new FormControl("", { + nonNullable: true, + }), + }); + + constructor( + private destroyRef: DestroyRef, + private route: ActivatedRoute, + private toastService: ToastService, + private letterQuery: DataEntryLetterSourceTextGQL, + private letterMutation: DataEntryUpdateLetterGQL + ) {} + + ngOnInit(): void { + this.letter$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((letter) => { + if (!letter) { + return; + } + this.form.patchValue(letter, { + emitEvent: false, + onlySelf: true, + }); + }); + + this.letter$ + .pipe( + switchMap(() => + this.form.valueChanges.pipe( + map(() => this.form.getRawValue()), + filter(() => this.form.valid), + debounceTime(300), + withLatestFrom(this.id$), + switchMap(([letter, id]) => + this.letterMutation.mutate({ + letterData: { + ...letter, + id, + }, + }) + ) + ) + ), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((result) => { + const errors = result.data?.updateLetter?.errors; + if (errors && errors.length > 0) { + this.toastService.show({ + body: errors.map((error) => error.messages).join("\n"), + type: "danger", + header: "Update failed", + }); + } + }); + } +} diff --git a/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.graphql b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.graphql new file mode 100644 index 00000000..3ad3fa1d --- /dev/null +++ b/frontend/src/app/data-entry/letter-form/letter-source-text-form/letter-source-text-form.graphql @@ -0,0 +1,9 @@ +query DataEntryLetterSourceText($id: ID!) { + letterDescription(id: $id) { + id + designators + book + chapter + page + } +} diff --git a/frontend/src/app/data-entry/letter-form/letter.graphql b/frontend/src/app/data-entry/letter-form/letter.graphql index 4f46fb8e..9ff63991 100644 --- a/frontend/src/app/data-entry/letter-form/letter.graphql +++ b/frontend/src/app/data-entry/letter-form/letter.graphql @@ -1,11 +1,21 @@ -query DataEntryLetter($id: ID!) { - letterDescription(id: $id) { - id - name - description - source { - id - name +query DataEntryLetterForm($id: ID!) { + letterDescription(id: $id) { + id + name + description + source { + id + name + } + } +} + +mutation DataEntryUpdateLetter($letterData: UpdateLetterInput!) { + updateLetter(letterData: $letterData) { + ok + errors { + field + messages + } } - } } diff --git a/frontend/src/app/data-entry/source/episode-preview/episode-preview.component.html b/frontend/src/app/data-entry/source/episode-preview/episode-preview.component.html index 6d665bd6..7adfc134 100644 --- a/frontend/src/app/data-entry/source/episode-preview/episode-preview.component.html +++ b/frontend/src/app/data-entry/source/episode-preview/episode-preview.component.html @@ -77,7 +77,10 @@ *ngFor="let gift of episode.gifts" class="inline-list-item" > - + {{ gift.name }} @@ -87,7 +90,12 @@ class="inline-list-item" >