Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit b89a622

Browse files
committed
add: mass reject application
1 parent 96d203b commit b89a622

3 files changed

Lines changed: 155 additions & 8 deletions

File tree

apps/admin-ui/src/i18n/messages.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ const translations: ITranslations = {
190190
errorFetchingData: ["Virhe haettaessa tietoja"],
191191
errorFetchingApplication: ["Virhe haettaessa hakemusta"],
192192
errorFetchingApplications: ["Virhe haettaessa hakemuksia"],
193+
errorRejectingApplication: ["Virhe hylätessä hakemusta"],
193194
// TODO describe what failed if you don't know why it failed
194195
functionFailedTitle: ["Toiminto epäonnistui"],
195196
unexpectedError: ["Odottamaton virhe"],
@@ -416,10 +417,14 @@ const translations: ITranslations = {
416417
authenticatedUser: ["Tunnistautunut käyttäjä"],
417418
emptyFilterPageName: ["hakemusta"],
418419
rejected: ["Hylätty"],
419-
btnReject: ["Hylkää"],
420-
btnRevert: ["Palauta"],
421-
btnRejectAll: ["Hylkää kaikki"],
422-
btnRestoreAll: ["Palauta kaikki"],
420+
btnRestore: ["Palauta koko hakemus"],
421+
btnReject: ["Hylkää koko hakemus"],
422+
section: {
423+
btnReject: ["Hylkää"],
424+
btnRestore: ["Palauta"],
425+
btnRejectAll: ["Hylkää kaikki"],
426+
btnRestoreAll: ["Palauta kaikki"],
427+
},
423428
headings: {
424429
id: ["id"],
425430
customer: ["Hakija"],

apps/admin-ui/src/spa/applications/[id]/index.tsx

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
type Maybe,
3333
type MutationRejectAllSectionOptionsArgs,
3434
type MutationRestoreAllSectionOptionsArgs,
35+
type MutationRejectAllApplicationOptionsArgs,
36+
type MutationRestoreAllApplicationOptionsArgs,
3537
} from "common/types/gql-types";
3638
import { formatDuration } from "common/src/common/util";
3739
import { convertWeekday, type Day } from "common/src/conversion";
@@ -54,7 +56,9 @@ import { TimeSelector } from "../TimeSelector";
5456
import {
5557
APPLICATION_ADMIN_QUERY,
5658
REJECT_ALL_SECTION_OPTIONS,
59+
REJECT_APPLICATION,
5760
RESTORE_ALL_SECTION_OPTIONS,
61+
RESTORE_APPLICATION,
5862
} from "../queries";
5963
import { getApplicantName, getApplicationStatusColor } from "@/helpers";
6064
// TODO move
@@ -166,7 +170,7 @@ const StyledAccordion = styled(Accordion).attrs({
166170

167171
const PreCard = styled.div`
168172
font-size: var(--fontsize-body-s);
169-
margin-bottom: var(--spacing-m);
173+
margin-bottom: var(--spacing-2-xs);
170174
`;
171175

172176
const EventSchedules = styled.div`
@@ -428,7 +432,9 @@ function RejectOptionButton({
428432
disabled={isDisabled}
429433
data-testid={`reject-btn-${option.pk}`}
430434
>
431-
{isRejected ? t("Application.btnRevert") : t("Application.btnReject")}
435+
{isRejected
436+
? t("Application.section.btnRestore")
437+
: t("Application.section.btnReject")}
432438
</Button>
433439
);
434440
}
@@ -525,8 +531,8 @@ function RejectAllOptionsButton({
525531
isLoading={isLoading}
526532
>
527533
{isRejected
528-
? t("Application.btnRestoreAll")
529-
: t("Application.btnRejectAll")}
534+
? t("Application.section.btnRestoreAll")
535+
: t("Application.section.btnRejectAll")}
530536
</Button>
531537
);
532538
}
@@ -694,6 +700,115 @@ function ApplicationSectionDetails({
694700
);
695701
}
696702

703+
function RejectApplicationButton({
704+
application,
705+
refetch,
706+
}: {
707+
application: ApplicationNode;
708+
refetch: () => Promise<ApolloQueryResult<Query>>;
709+
}): JSX.Element {
710+
const { t } = useTranslation();
711+
const { notifyError } = useNotification();
712+
713+
const [rejectionMutation, { loading: isRejectionLoading }] = useMutation<
714+
Query,
715+
MutationRejectAllApplicationOptionsArgs
716+
>(REJECT_APPLICATION);
717+
718+
const [restoreMutation, { loading: isRestoreLoading }] = useMutation<
719+
Query,
720+
MutationRestoreAllApplicationOptionsArgs
721+
>(RESTORE_APPLICATION);
722+
723+
const isLoading = isRejectionLoading || isRestoreLoading;
724+
725+
const updateApplication = async (
726+
pk: Maybe<number> | undefined,
727+
shouldReject: boolean
728+
) => {
729+
if (pk == null) {
730+
return;
731+
}
732+
if (isLoading) {
733+
return;
734+
}
735+
736+
const mutation = shouldReject ? rejectionMutation : restoreMutation;
737+
try {
738+
await mutation({
739+
variables: {
740+
input: {
741+
pk,
742+
},
743+
},
744+
});
745+
refetch();
746+
} catch (err) {
747+
const mutationErrors = getValidationErrors(err);
748+
if (mutationErrors.length > 0) {
749+
// TODO handle other codes also
750+
const isInvalidState = mutationErrors.find(
751+
(e) => e.code === "CANNOT_REJECT_APPLICATION_OPTIONS"
752+
);
753+
if (isInvalidState) {
754+
notifyError(t("errors.cantRejectAlreadyAllocated"));
755+
} else {
756+
// TODO this should show them with cleaner formatting (multiple errors)
757+
// TODO these should be translated
758+
const message = mutationErrors.map((e) => e.message).join(", ");
759+
notifyError(t("errors.formValidationError", { message }));
760+
}
761+
} else {
762+
notifyError(t("errors.errorRejectingApplication"));
763+
}
764+
}
765+
};
766+
767+
const handleRejectAll = async () => {
768+
updateApplication(application.pk, true);
769+
};
770+
771+
const handleRestoreAll = async () => {
772+
updateApplication(application.pk, false);
773+
};
774+
775+
// codegen types allow undefined so have to do this for debugging
776+
if (application.applicationSections == null) {
777+
// eslint-disable-next-line no-console
778+
console.warn("application.applicationSections is null", application);
779+
}
780+
if (application.status == null) {
781+
// eslint-disable-next-line no-console
782+
console.warn("application.status is null", application);
783+
}
784+
785+
const isInAllocation =
786+
application.status === ApplicationStatusChoice.InAllocation;
787+
const hasBeenAllocated =
788+
application.applicationSections?.some((section) =>
789+
section.reservationUnitOptions.some(
790+
(option) => option.allocatedTimeSlots?.length > 0
791+
)
792+
) ?? false;
793+
const canReject = isInAllocation && !hasBeenAllocated;
794+
const isRejected =
795+
application.applicationSections?.every((section) =>
796+
section.reservationUnitOptions.every((option) => option.rejected)
797+
) ?? false;
798+
799+
return (
800+
<Button
801+
size="small"
802+
variant="secondary"
803+
theme="black"
804+
onClick={() => (isRejected ? handleRestoreAll() : handleRejectAll())}
805+
disabled={!canReject}
806+
>
807+
{isRejected ? t("Application.btnRestore") : t("Application.btnReject")}
808+
</Button>
809+
);
810+
}
811+
697812
function ApplicationDetails({
698813
applicationPk,
699814
}: {
@@ -789,12 +904,19 @@ function ApplicationDetails({
789904
{t("Application.applicationReceivedTime")}{" "}
790905
{formatDate(application.lastModifiedDate, "d.M.yyyy HH:mm")}
791906
</PreCard>
907+
<div style={{ marginBottom: "var(--spacing-s)" }}>
908+
<RejectApplicationButton
909+
application={application}
910+
refetch={refetch}
911+
/>
912+
</div>
792913
<Card
793914
theme={{
794915
"--background-color": "var(--color-black-5)",
795916
"--padding-horizontal": "var(--spacing-m)",
796917
"--padding-vertical": "var(--spacing-m)",
797918
}}
919+
style={{ marginBottom: "var(--spacing-m)" }}
798920
>
799921
<CardContentContainer>
800922
<DefinitionList>

apps/admin-ui/src/spa/applications/queries.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,23 @@ export const RESTORE_ALL_SECTION_OPTIONS = gql`
3131
}
3232
}
3333
`;
34+
35+
export const REJECT_APPLICATION = gql`
36+
mutation rejectAllApplicationOptions(
37+
$input: RejectAllApplicationOptionsMutationInput!
38+
) {
39+
rejectAllApplicationOptions(input: $input) {
40+
pk
41+
}
42+
}
43+
`;
44+
45+
export const RESTORE_APPLICATION = gql`
46+
mutation restoreAllApplicationOptions(
47+
$input: RestoreAllApplicationOptionsMutationInput!
48+
) {
49+
restoreAllApplicationOptions(input: $input) {
50+
pk
51+
}
52+
}
53+
`;

0 commit comments

Comments
 (0)