Skip to content
Merged
36 changes: 24 additions & 12 deletions kompassi-v2-frontend/src/__generated__/gql.ts

Large diffs are not rendered by default.

44 changes: 36 additions & 8 deletions kompassi-v2-frontend/src/__generated__/graphql.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import {
Markdown,
SignInRequired,
SubmitButton,
FormattedDateTime,
} from "@con2/components";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";

import { Alert } from "react-bootstrap";
import { submit } from "../../../../../surveys/[surveySlug]/responses/[responseId]/edit/actions";
import { graphql } from "@/__generated__";
import { SurveyPurpose } from "@/__generated__/graphql";
import { getClient } from "@/apolloClient";
import { auth } from "@/auth";
import { validateFields } from "@/components/forms/models";
import { SchemaForm } from "@/components/forms/SchemaForm";
import ProgramAdminView from "@/components/program/ProgramAdminView";
import getPageTitle from "@/helpers/getPageTitle";
import { getTranslations } from "@/translations";

graphql(`
fragment ProgramFormResponseEdit on FullResponseType {
id
revisionCreatedAt
originalCreatedBy {
fullName
}
values
form {
title
description
fields
survey {
slug
profileFieldSelector {
...FullProfileFieldSelector
}
}
}
}
`);

const query = graphql(`
query ProgramFormResponseEditPage(
$eventSlug: String!
$surveySlug: String!
$responseId: String!
) {
event(slug: $eventSlug) {
name
slug
timezone

forms {
survey(slug: $surveySlug, app: PROGRAM) {
purpose

response(id: $responseId) {
...ProgramFormResponseEdit
}
}
}
}
}
`);

interface Props {
params: Promise<{
locale: string;
eventSlug: string;
surveySlug: string;
responseId: string;
}>;
searchParams: Promise<Record<string, string>>;
}

interface Values {
title?: string;
description?: string;
}

export async function generateMetadata(props: Props) {
const params = await props.params;
const { locale, eventSlug, surveySlug, responseId } = params;
const translations = getTranslations(locale);

// TODO encap
const session = await auth();
if (!session) {
return translations.SignInRequired.metadata;
}

const t = translations.Survey;

const { data } = await getClient().query({
query,
variables: { eventSlug, surveySlug, responseId },
});

if (!data.event?.forms?.survey?.response) {
notFound();
}

const values: Values = data.event.forms.survey.response.values as any;

const title = getPageTitle({
viewTitle: t.responseDetailTitle,
subject: values.title || "",
event: data.event,
translations,
});

return {
title,
};
}

export const revalidate = 0;

export default async function ProgramFormResponseEditPage(props: Props) {
const searchParams = await props.searchParams;
const params = await props.params;
const { locale, eventSlug, surveySlug, responseId } = params;
const translations = getTranslations(locale);
const t = translations.Survey;
const session = await auth();

// TODO encap
if (!session) {
return (
<SignInRequired
messages={translations.SignInRequired}
providerId="kompassi"
locale={locale}
/>
);
}

const { data } = await getClient().query({
query,
variables: { eventSlug, surveySlug, responseId },
});

if (!data.event?.forms?.survey?.response) {
notFound();
}

const survey = data.event.forms.survey;

if (survey.purpose === SurveyPurpose.Default) {
redirect(`/${eventSlug}/program-offers/${responseId}/edit`);
}

const response = survey.response;
if (!response) {
notFound();
}

const { event } = data;
const { revisionCreatedAt, originalCreatedBy, form } = response;
const { fields, survey: formSurvey, description, title } = form;

const values: Record<string, any> = response.values ?? {};

validateFields(fields);

return (
<ProgramAdminView
translations={translations}
event={data.event}
active="programForms"
searchParams={searchParams}
actions={
<Link
className="btn btn-outline-danger"
href={`/${eventSlug}/program-forms/${surveySlug}/responses/${responseId}`}
>
❌ {t.actions.editResponse.cancel}
</Link>
}
>
<Alert variant="warning" className="mt-4">
{t.actions.editResponse.editingOthers(
<FormattedDateTime value={revisionCreatedAt} locale={locale} />,
originalCreatedBy?.fullName,
)}
</Alert>

<h3 className="mb-3 mt-3">{title}</h3>
<Markdown input={description} />

<form
action={submit.bind(
null,
locale,
event.slug,
formSurvey.slug,
response.id,
`${eventSlug}/program-forms/${surveySlug}/responses`,
)}
>
<SchemaForm
fields={fields}
messages={translations.SchemaForm}
values={values}
/>
<SubmitButton>{translations.Common.submit}</SubmitButton>
</form>
</ProgramAdminView>
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { SignInRequired } from "@con2/components";
import { ModalButton, SignInRequired } from "@con2/components";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";

import { ButtonGroup } from "react-bootstrap";
import { updateResponseDimensions } from "../../../../surveys/[surveySlug]/responses/[responseId]/actions";
import { deleteSurveyResponses } from "../../../../surveys/[surveySlug]/responses/actions";
import { graphql } from "@/__generated__";
import { SurveyPurpose } from "@/__generated__/graphql";
import { getClient } from "@/apolloClient";
Expand Down Expand Up @@ -170,6 +172,7 @@ export default async function ProgramFormResponsePage(props: Props) {
const surveyDimensions = survey.dimensions;
const dimensionsReadOnly = !!supersededBy;

const { canEdit, canDelete } = response;
const responsesBaseUrl = `/${eventSlug}/program-forms/${surveySlug}/responses`;

return (
Expand All @@ -178,6 +181,36 @@ export default async function ProgramFormResponsePage(props: Props) {
event={data.event}
active="programForms"
searchParams={searchParams}
actions={
<ButtonGroup>
<ModalButton
className="btn btn-outline-danger"
label={t.actions.deleteResponse.label + "…"}
title={t.actions.deleteResponse.title}
messages={t.actions.deleteResponse.modalActions}
disabled={!canDelete}
action={deleteSurveyResponses.bind(
null,
locale,
eventSlug,
surveySlug,
[responseId],
searchParams,
`${eventSlug}/program-forms/${surveySlug}/responses`,
)}
>
{t.actions.deleteResponse.confirmation}
</ModalButton>

<Link
className={`btn btn-outline-primary ${canEdit ? "" : "disabled"}`}
href={`/${locale}/${eventSlug}/program-forms/${surveySlug}/responses/${responseId}/edit`}
title={t.actions.editResponse.title}
>
{t.actions.editResponse.label}
</Link>
</ButtonGroup>
}
>
<Link className="link-subtle" href={responsesBaseUrl}>
&lt; {t.responseListTitle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import {
Column,
DataTable,
DimensionFilters,
FormattedDateTime,
ModalButton,
SignInRequired,
UploadedFileLink,
FormattedDateTime,
} from "@con2/components";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { Fragment } from "react";
import { Fragment, ReactNode } from "react";

import { toggleSurveyResponseSubscription } from "../../../surveys/[surveySlug]/responses/actions";
import { ResponseListActions } from "../../../surveys/[surveySlug]/responses/ResponseListActions";
import { graphql } from "@/__generated__";
import {
CanResponsesBeDeleted,
ProgramFormResponseFragment,
SurveyPurpose,
} from "@/__generated__/graphql";
Expand All @@ -29,6 +29,11 @@ import ProgramAdminView from "@/components/program/ProgramAdminView";
import { kompassiBaseUrl } from "@/config";
import getPageTitle from "@/helpers/getPageTitle";
import { getTranslations } from "@/translations";
import {
deleteSurveyResponses,
toggleSurveyResponseSubscription,
} from "../../../surveys/[surveySlug]/responses/actions";
import { ResponseListActions } from "../../../surveys/[surveySlug]/responses/ResponseListActions";

// this fragment is just to give a name to the type so that we can import it from generated
graphql(`
Expand Down Expand Up @@ -77,6 +82,7 @@ const query = graphql(`
}

countResponses
responsesDeletionStatus

responses(filters: $filters) {
...ProgramFormResponse
Expand Down Expand Up @@ -195,15 +201,12 @@ export default async function ProgramFormResponsesPage(props: Props) {
</Link>
),
},
];

if (anonymity === "NAME_AND_EMAIL") {
columns.push({
{
slug: "revisionCreatedBy",
title: t.attributes.originalCreatedBy,
getCellContents: (row) => row.revisionCreatedBy?.displayName || "",
});
}
},
];

keyFields.forEach((keyField) => {
columns.push({
Expand Down Expand Up @@ -248,6 +251,23 @@ export default async function ProgramFormResponsesPage(props: Props) {
(survey) => survey.slug === surveySlug,
);

const canRemoveResponses =
survey.responsesDeletionStatus === CanResponsesBeDeleted.Yes;

// Program forms are protected by the event-wide program preferences toggle
// rather than a per-survey one, so the message for that reason differs.
const deletionStatusReasons = {
...t.actions.deleteVisibleResponses.reasons,
NO_PROTECTED: translations.Program.ProgramForm.actions.responsesProtected,
};

const cannotRemoveResponsesReason: string | ReactNode | null =
canRemoveResponses
? null
: responses.length < 1
? t.actions.deleteVisibleResponses.noResponsesToDelete
: deletionStatusReasons[survey.responsesDeletionStatus];

return (
<ProgramAdminView
translations={translations}
Expand All @@ -269,16 +289,32 @@ export default async function ProgramFormResponsesPage(props: Props) {
toggleSubscription: t.actions.toggleSubscription,
exportDropdown: t.actions.exportDropdown,
}}
/>
>
<ModalButton
title={t.actions.deleteVisibleResponses.title}
messages={t.actions.deleteVisibleResponses.modalActions}
action={
canRemoveResponses
? deleteSurveyResponses.bind(
null,
locale,
eventSlug,
surveySlug,
responses.map((response) => response.id),
searchParams,
`${eventSlug}/program-forms/${surveySlug}/responses`,
)
: undefined
}
className="btn btn-outline-danger"
>
{canRemoveResponses
? t.actions.deleteVisibleResponses.confirmation(responses.length)
: cannotRemoveResponsesReason}
</ModalButton>
</ResponseListActions>
}
>
<Link className="link-subtle" href={`/${eventSlug}/program-forms`}>
&lt;{" "}
{translations.Program.ProgramForm.actions.returnToProgramFormList(
data.event.name,
)}
</Link>

<h3 className="mt-4">
{t.responseListTitle}: {survey.title}
</h3>
Expand Down
Loading