From 236866d93e31a436ddd2dbe9d313a09a62b4391c Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Sat, 14 May 2022 16:59:56 +0200 Subject: [PATCH] sci-types-project: Managing sponsors. --- .../ui/SciProjectAuthoringSteps.java | 3 +- .../ui/SciProjectFundingSponsors.java | 102 ++++++++++++ .../sciproject/ui/SciProjectFundingStep.java | 41 ++++- .../sciproject/ui/sciproject-funding.xhtml | 111 +++++++++---- .../sciproject/ui/SciProjectBundle.properties | 5 + .../ui/SciProjectBundle_de.properties | 5 + .../main/typescript/sciproject-contacts.ts | 4 +- .../main/typescript/sciproject-sponsoring.ts | 155 ++++++++++++++++++ sci-types-project/webpack.config.js | 5 +- 9 files changed, 383 insertions(+), 48 deletions(-) create mode 100644 sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingSponsors.java create mode 100644 sci-types-project/src/main/typescript/sciproject-sponsoring.ts diff --git a/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectAuthoringSteps.java b/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectAuthoringSteps.java index 2931de5..b039b19 100644 --- a/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectAuthoringSteps.java +++ b/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectAuthoringSteps.java @@ -29,7 +29,8 @@ public class SciProjectAuthoringSteps implements MvcAuthoringSteps { SciProjectDescriptionStepResources.class, SciProjectDescriptionStepService.class, SciProjectFundingStepResources.class, - SciProjectFundingStepService.class + SciProjectFundingStepService.class, + SciProjectFundingSponsors.class ); } diff --git a/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingSponsors.java b/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingSponsors.java new file mode 100644 index 0000000..a2f351a --- /dev/null +++ b/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingSponsors.java @@ -0,0 +1,102 @@ +package org.scientificcms.contenttypes.sciproject.ui; + +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentItemRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.ui.contentsections.ContentSectionsUi; +import org.librecms.ui.contentsections.documents.MvcAuthoringSteps; +import org.scientificcms.contenttypes.sciproject.SciProject; +import org.scientificcms.contenttypes.sciproject.Sponsoring; +import org.scientificcms.contenttypes.sciproject.SponsoringRepository; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; +import javax.ws.rs.Consumes; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Path(MvcAuthoringSteps.PATH_PREFIX + "sciproject-funding-sponsors") +public class SciProjectFundingSponsors { + + @Inject + private SponsoringRepository sponsoringRepo; + + @Inject + private ContentItemRepository itemRepo; + + @Inject + private ContentSectionsUi sectionsUi; + + @POST + @Path("/save-order") + @Consumes(MediaType.APPLICATION_JSON) + @Transactional(Transactional.TxType.REQUIRED) + public Response saveOrder( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + final List order + ) { + final ContentSection contentSection = sectionsUi + .findContentSection(sectionIdentifier) + .orElseThrow( + () -> new NotFoundException( + String.format( + "No content identifed by %s found.", + sectionIdentifier + ) + ) + ); + + final ContentItem document = itemRepo + .findByPath(contentSection, documentPath) + .orElseThrow( + () -> new NotFoundException( + String.format( + "No document for path %s in section %s.", + documentPath, + contentSection.getLabel() + ) + ) + ); + + if (!(document instanceof SciProject)) { + throw new NotFoundException( + String.format( + "No SciProject for path %s in section %s.", + documentPath, + contentSection.getLabel() + ) + ); + } + + final Map orderMap = new HashMap<>(); + for (int i = 0; i < order.size(); i++) { + orderMap.put(Long.parseLong(order.get(i)), (long) i); + } + + final SciProject project = (SciProject) document; + for (final Sponsoring sponsoring : project.getSponsoring()) { + sponsoring.setOrder(orderMap.get(sponsoring.getSponsoringId())); + sponsoringRepo.save(sponsoring); + } + + return Response.ok().build(); + } + +} diff --git a/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingStep.java b/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingStep.java index 4fc7d82..07e5875 100644 --- a/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingStep.java +++ b/sci-types-project/src/main/java/org/scientificcms/contenttypes/sciproject/ui/SciProjectFundingStep.java @@ -1,5 +1,7 @@ package org.scientificcms.contenttypes.sciproject.ui; +import org.libreccm.api.Identifier; +import org.libreccm.api.IdentifierParser; import org.libreccm.l10n.GlobalizationHelper; import org.libreccm.security.AuthorizationRequired; import org.libreccm.ui.BaseUrl; @@ -58,7 +60,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep { @Inject private AssetRepository assetRepo; - + @Inject private BaseUrl baseUrl; @@ -73,7 +75,10 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep { @Context private HttpServletRequest request; - + + @Inject + private IdentifierParser identifierParser; + @Inject private ItemPermissionChecker itemPermissionChecker; @@ -496,7 +501,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep { @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) final String documentPath, @FormParam("sponsorUuid") - final String sponsorUuid, + final String sponsorUuidParam, @FormParam("fundingCode") final String fundingCode ) { @@ -509,11 +514,29 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep { } if (itemPermissionChecker.canEditItem(getProject())) { - final Optional result = assetRepo - .findByUuidAndType(sponsorUuid, Organization.class); - + final Optional result; + final Identifier identifier = identifierParser.parseIdentifier( + sponsorUuidParam + ); + switch (identifier.getType()) { + case UUID: + result = assetRepo.findByUuidAndType( + identifier.getIdentifier(), Organization.class + ); + break; + case ID: + result = assetRepo.findById( + Long.parseLong(identifier.getIdentifier()), + Organization.class + ); + break; + default: + models.put("sponsorNotFound", sponsorUuidParam); + return showStep(sectionIdentifier, documentPath); + } + if (!result.isPresent()) { - models.put("sponsorNotFound", sponsorUuid); + models.put("sponsorNotFound", sponsorUuidParam); return showStep(sectionIdentifier, documentPath); } @@ -582,7 +605,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep { } @POST - @Path("/sponsoring/remove") + @Path("/sponsoring/{sponsoringId}/remove") @Transactional(Transactional.TxType.REQUIRED) @AuthorizationRequired public String removeSponsoring( @@ -590,7 +613,7 @@ public class SciProjectFundingStep extends AbstractMvcAuthoringStep { final String sectionIdentifier, @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) final String documentPath, - @FormParam("sponsoringId") + @PathParam("sponsoringId") final String sponsoringId ) { try { diff --git a/sci-types-project/src/main/resources/WEB-INF/views/org/scientificcms/contenttypes/sciproject/ui/sciproject-funding.xhtml b/sci-types-project/src/main/resources/WEB-INF/views/org/scientificcms/contenttypes/sciproject/ui/sciproject-funding.xhtml index aa867bd..688b12f 100644 --- a/sci-types-project/src/main/resources/WEB-INF/views/org/scientificcms/contenttypes/sciproject/ui/sciproject-funding.xhtml +++ b/sci-types-project/src/main/resources/WEB-INF/views/org/scientificcms/contenttypes/sciproject/ui/sciproject-funding.xhtml @@ -13,6 +13,25 @@

#{SciProjectMessageBundle['funding_step.header']}

+ +
+ #{SciProjectMessageBundle.getMessage('funding.errors.sponsor_not_found', [sponsorNotFound])} +
+
+ + + + +
+ - + +
@@ -84,27 +117,40 @@ - - + +
#{SciProjectMessageBundle['sponsoring.cols.sponsor']}
#{sponsoring.sponsor}
+ + + + #{sponsoring.sponsor} + #{sponsoring.fundingCode} @@ -154,10 +201,10 @@
- - - +
+ + diff --git a/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle.properties b/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle.properties index 217c841..8a18d6d 100644 --- a/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle.properties +++ b/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle.properties @@ -181,3 +181,8 @@ contact.add.title=Add contact memberships.add.title=Add member description_step.errors.person_not_found=Selected person {0} not found. description_step.errors.illegal_member_status_value=The status value {0} is not valid. +sponsoring.order.save=Save order +sponsoring.move.button=Drag to order sponsors +funding.errors.sponsor_not_found=The selected sponsor {0} was not found. +sponsors.sort.errors.general=Error sorting sponsors +sponsors.sort.errors.save=Failed to save order of sponsors. diff --git a/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle_de.properties b/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle_de.properties index f565859..9827564 100644 --- a/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle_de.properties +++ b/sci-types-project/src/main/resources/org/scientificcms/contenttypes/sciproject/ui/SciProjectBundle_de.properties @@ -181,3 +181,8 @@ contact.add.title=Kontakt hinzuf\u00fcgen memberships.add.title=Mitglied hinzuf\u00fcgen description_step.errors.person_not_found=Die ausgew\u00e4hlte Person {0} wurde nicht gefunden. description_step.errors.illegal_member_status_value=Der Status {0} wird nicht unterst\u00fctzt. +sponsoring.order.save=Sortierung speichern +sponsoring.move.button=Zum sortieren Sponsoren ziehen +funding.errors.sponsor_not_found=Der ausgew\u00e4hlte Sponsor {0} wurde nicht gefunden. +sponsors.sort.errors.general=Fehler beim Sortieren der Sponsoren +sponsors.sort.errors.save=Speichern der Sortierung der Sponsoren fehlgeschlagen. diff --git a/sci-types-project/src/main/typescript/sciproject-contacts.ts b/sci-types-project/src/main/typescript/sciproject-contacts.ts index dd2b271..9d74d66 100644 --- a/sci-types-project/src/main/typescript/sciproject-contacts.ts +++ b/sci-types-project/src/main/typescript/sciproject-contacts.ts @@ -106,7 +106,7 @@ function saveOrder() { spinner?.classList.toggle("d-none"); } throw Error( - `Failed to save attachments order. Response status: ${response.status}, statusText: ${response.statusText}` + `Failed to save contacts order. Response status: ${response.status}, statusText: ${response.statusText}` ); } }) @@ -122,7 +122,7 @@ function saveOrder() { saveIcon?.classList.toggle("d-none"); spinner?.classList.toggle("d-none"); } - throw new Error(`Failed to save attachments order: ${error}`); + throw new Error(`Failed to save contacts order: ${error}`); }); } diff --git a/sci-types-project/src/main/typescript/sciproject-sponsoring.ts b/sci-types-project/src/main/typescript/sciproject-sponsoring.ts new file mode 100644 index 0000000..c2b4b6e --- /dev/null +++ b/sci-types-project/src/main/typescript/sciproject-sponsoring.ts @@ -0,0 +1,155 @@ +import Sortable, { SortableEvent } from "sortablejs"; + +let sponsoringSortable: Sortable; + +document.addEventListener("DOMContentLoaded", function (event) { + const sponsoringTable = document.querySelector( + "#sciproject-sponsoring-table tbody" + ); + + if (sponsoringTable) { + sponsoringSortable = initSponsoringTable( + sponsoringTable as HTMLElement + ); + } + + const saveOrderButtons = document.querySelectorAll( + ".sponsoring-save-order-button" + ); + for (let i = 0; i < saveOrderButtons.length; i++) { + saveOrderButtons[i].addEventListener("click", saveOrder); + } +}); + +function initSponsoringTable(sponsoringTable: HTMLElement): Sortable { + return new Sortable(sponsoringTable, { + animation: 150, + group: "sciproject-sponsor", + handle: ".cms-sort-handle", + onEnd: enableSaveButtons, + }); +} + +function enableSaveButtons(event: SortableEvent) { + const saveOrderButtons = document.querySelectorAll( + ".sponsoring-save-order-button" + ); + for (let i = 0; i < saveOrderButtons.length; i++) { + const saveOrderButton: HTMLButtonElement = saveOrderButtons[ + i + ] as HTMLButtonElement; + saveOrderButton.disabled = false; + } +} + +function saveOrder() { + const sponsoringTable = document.querySelector( + "#sciproject-sponsoring-table" + ); + + if (!sponsoringTable) { + showGeneralError(); + throw Error("sciproject-sponsoring-table not found."); + } + + const saveUrl = sponsoringTable.getAttribute("data-saveUrl"); + if (!saveUrl) { + showGeneralError(); + throw Error( + "data-saveUrl on sciproject-sponsoring-table is missing or empty" + ); + } + + const saveOrderButtons = document.querySelectorAll( + ".sponsoring-save-order-button" + ); + for (let i = 0; i < saveOrderButtons.length; i++) { + const saveOrderButton: HTMLButtonElement = saveOrderButtons[ + i + ] as HTMLButtonElement; + saveOrderButton.disabled = true; + const saveIcon = saveOrderButton.querySelector("save-icon"); + const spinner = saveOrderButton.querySelector(".save-spinner"); + saveIcon?.classList.toggle("d-none"); + spinner?.classList.toggle("d-none"); + } + + const headers = new Headers(); + headers.append("Content-Type", "application/json"); + fetch(saveUrl, { + credentials: "include", + body: JSON.stringify(sponsoringSortable.toArray()), + headers, + method: "POST", + }) + .then((response) => { + if (response.ok) { + for (let i = 0; i < saveOrderButtons.length; i++) { + const saveOrderButton: HTMLButtonElement = saveOrderButtons[ + i + ] as HTMLButtonElement; + const saveIcon = + saveOrderButton.querySelector(".save-icon"); + const spinner = + saveOrderButton.querySelector(".save-spinner"); + saveIcon?.classList.toggle("d-none"); + spinner?.classList.toggle("d-none"); + } + } else { + showSaveError(); + for (let i = 0; i < saveOrderButtons.length; i++) { + const saveOrderButton: HTMLButtonElement = saveOrderButtons[ + i + ] as HTMLButtonElement; + saveOrderButton.disabled = false; + const saveIcon = + saveOrderButton.querySelector(".save-icon"); + const spinner = + saveOrderButton.querySelector(".save-spinner"); + saveIcon?.classList.toggle("d-none"); + spinner?.classList.toggle("d-none"); + } + throw Error( + `Failed to save sponsor order. Response status: ${response.status}, statusText: ${response.statusText}` + ); + } + }) + .catch((error) => { + showSaveError(); + for (let i = 0; i < saveOrderButtons.length; i++) { + const saveOrderButton: HTMLButtonElement = saveOrderButtons[ + i + ] as HTMLButtonElement; + saveOrderButton.disabled = false; + const saveIcon = saveOrderButton.querySelector(".save-icon"); + const spinner = saveOrderButton.querySelector(".save-spinner"); + saveIcon?.classList.toggle("d-none"); + spinner?.classList.toggle("d-none"); + } + throw new Error(`Failed to save sponsor order: ${error}`); + }); +} + +function showGeneralError(): void { + const alertTemplate = document.querySelector( + "#sciproject-sponsoring-sort-error-general" + ) as HTMLTemplateElement; + const alert = alertTemplate.content.cloneNode(true) as Element; + + const container = document.querySelector("#messages"); + if (container) { + container.appendChild(alert); + } +} + +function showSaveError(): void { + const alertTemplate = document.querySelector( + "#sciproject-sponsoring-sort-error-save" + ) as HTMLTemplateElement; + const alert = alertTemplate.content.cloneNode(true) as Element; + + const container = document.querySelector("#messages"); + if (container) { + container.appendChild(alert); + } +} diff --git a/sci-types-project/webpack.config.js b/sci-types-project/webpack.config.js index 720014d..c57c414 100644 --- a/sci-types-project/webpack.config.js +++ b/sci-types-project/webpack.config.js @@ -5,11 +5,8 @@ module.exports = { chunkIds: false }, entry: { - // "sciproject-description": "./src/main/typescript/sciproject-description.ts", "sciproject-contacts": "./src/main/typescript/sciproject-contacts.ts", - // "sciproject-fundingtext": "./src/main/typescript/sciproject-fundingtext.ts", - // "sciproject-fundingvolume": "./src/main/typescript/sciproject-fundingvolume.ts", - //"sciproject-sponsoring": "./src/main/typescript/sciproject-sponsoring.ts" + "sciproject-sponsoring": "./src/main/typescript/sciproject-sponsoring.ts" }, output: { filename: "[name].js",