Skip to content

Commit

Permalink
✨(backoffice) add template name field into ContractDefinition edit view
Browse files Browse the repository at this point in the history
We recenty create a new contract template. From now, there was only one, so
the contract definition edit view in the back office application did not display
input for this field.
  • Loading branch information
jbpenrath committed Dec 18, 2024
1 parent c7bec8b commit a7151b3
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 40 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ and this project adheres to

## [Unreleased]

### Added

- Add template name field into ContractDefinition edit view
in the Back office application

### Changed

- Prevent to index certificate verification page
- Update the model `Certificate` ordering to descending order
on the field `issued_on`

## [2.11.0] - 2024-12-11

Expand All @@ -28,8 +35,6 @@ and this project adheres to

### Changed

- Update the model `Certificate` ordering to descending order
on the field `issued_on`
- Update product endpoint to CRUD `teachers`, `skills`
and `certification level` fields

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Props = RHFSelectProps & {

export function RHFCertificateDefinitionTemplates({ name }: Props) {
const intl = useIntl();
const languages = useQuery({
const templates = useQuery({
queryKey: ["certificateDefinitionTemplates"],
staleTime: Infinity,
gcTime: Infinity,
Expand All @@ -34,9 +34,9 @@ export function RHFCertificateDefinitionTemplates({ name }: Props) {
return (
<RHFSelect
data-testid="template-select"
disabled={languages.isLoading}
disabled={templates.isLoading}
name={name}
options={languages.data ?? []}
options={templates.data ?? []}
label={intl.formatMessage(messages.templateLabel)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import { genericUpdateFormError } from "@/utils/forms";
import { useContractDefinitions } from "@/hooks/useContractDefinitions/useContractDefinitions";
import {
ContractDefinition,
ContractDefinitionFormValues,
ContractDefinitionTemplate,
DTOContractDefinition,
} from "@/services/api/models/ContractDefinition";
import { MarkdownComponent } from "@/components/presentational/inputs/markdown/MardownComponent";
import { removeEOL } from "@/utils/string";
import { RHFContractDefinitionLanguage } from "@/components/templates/contract-definition/inputs/RHFContractDefinitionLanguage";
import RHFContractDefinitionName from "@/components/templates/contract-definition/inputs/RHFContractDefinitionName";
import { RHFValuesChange } from "@/components/presentational/hook-form/RFHValuesChange";
import { useFormSubmit } from "@/hooks/form/useFormSubmit";

Expand Down Expand Up @@ -63,6 +66,17 @@ const messages = defineMessages({
},
});

const FORM_VALIDATION_SCHEMA = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
body: Yup.string().required(),
appendix: Yup.string().defined(),
language: Yup.string().required(),
name: Yup.string()
.oneOf([...Object.values(ContractDefinitionTemplate), ""])
.required(),
});

interface Props {
afterSubmit?: (contractDefinition: ContractDefinition) => void;
contractDefinition?: ContractDefinition;
Expand All @@ -82,29 +96,18 @@ export function ContractDefinitionForm({
const defaultContractDefinition =
contractDefinition ?? props.fromContractDefinition;

const RegisterSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
body: Yup.string().required(),
appendix: Yup.string(),
language: Yup.string().required(),
name: Yup.string().required(),
});

const getDefaultValues = () => {
return {
title: defaultContractDefinition?.title ?? "",
description: removeEOL(defaultContractDefinition?.description),
body: removeEOL(defaultContractDefinition?.body),
appendix: removeEOL(defaultContractDefinition?.appendix),
name: "contract_definition",
language: defaultContractDefinition?.language ?? "fr-fr",
};
const defaultValues: ContractDefinitionFormValues = {
title: defaultContractDefinition?.title ?? "",
description: removeEOL(defaultContractDefinition?.description) ?? "",
body: removeEOL(defaultContractDefinition?.body) ?? "",
appendix: removeEOL(defaultContractDefinition?.appendix) ?? "",
name: defaultContractDefinition?.name ?? "",
language: defaultContractDefinition?.language ?? "fr-fr",
};

const methods = useForm({
resolver: yupResolver(RegisterSchema),
defaultValues: getDefaultValues(),
resolver: yupResolver(FORM_VALIDATION_SCHEMA),
defaultValues,
});

const updateFormError = (
Expand All @@ -113,7 +116,7 @@ export function ContractDefinitionForm({
genericUpdateFormError(errors, methods.setError);
};

const onSubmit = (values: DTOContractDefinition) => {
const onSubmit = (values: ContractDefinitionFormValues) => {
if (contractDefinition) {
contractDefinitionQuery.methods.update(
{ id: contractDefinition.id, ...values },
Expand Down Expand Up @@ -168,6 +171,9 @@ export function ContractDefinitionForm({
<Grid size={12}>
<RHFContractDefinitionLanguage name="language" />
</Grid>
<Grid size={12}>
<RHFContractDefinitionName name="name" />
</Grid>
<Grid size={12}>
<RHFTextField
name="description"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useIntl, defineMessages } from "react-intl";
import { useQuery } from "@tanstack/react-query";
import {
RHFSelect,
RHFSelectProps,
} from "@/components/presentational/hook-form/RHFSelect";
import { ContractDefinitionRepository } from "@/services/repositories/contract-definition/ContractDefinitionRepository";

const messages = defineMessages({
inputLabel: {
id: "components.templates.contractDefinitions.inputs.RHFContractDefinitionName.inputLabel",
defaultMessage: "Template name",
description: 'Label for the "name" input',
},
});

type Props = RHFSelectProps & {
name: string;
};

function RHFContractDefinitionName({ name }: Props) {
const intl = useIntl();
const templateNames = useQuery({
queryKey: ["contractDefinitionTemplates"],
staleTime: Infinity,
gcTime: Infinity,
queryFn: async () => {
return ContractDefinitionRepository.getAllTemplates();
},
});

return (
<RHFSelect
data-testid="contract-definition-template-name-input"
disabled={templateNames.isLoading}
name={name}
options={templateNames.data ?? []}
label={intl.formatMessage(messages.inputLabel)}
/>
);
}

export default RHFContractDefinitionName;
20 changes: 16 additions & 4 deletions src/frontend/admin/src/services/api/models/ContractDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ import { Optional } from "@/types/utils";
export type ContractDefinition = {
id: string;
title: string;
description?: string;
body?: string;
appendix?: string;
description: string;
body: string;
appendix: string;
language: string;
name: string;
name: ContractDefinitionTemplate;
};

export type ContractDefinitionFormValues = Omit<
ContractDefinition,
"id" | "name"
> & {
name: ContractDefinitionTemplate | "";
};

export type DTOContractDefinition = Optional<ContractDefinition, "id">;

export enum ContractDefinitionTemplate {
DEFAULT = "contract_definition_default",
UNICAMP = "contract_definition_unicamp",
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { faker } from "@faker-js/faker";
import { ContractDefinition } from "@/services/api/models/ContractDefinition";
import {
ContractDefinition,
ContractDefinitionTemplate,
} from "@/services/api/models/ContractDefinition";

const build = (): ContractDefinition => {
return {
id: faker.string.uuid(),
title: faker.company.name(),
description: faker.lorem.lines(2),
language: "fr-fr",
name: "contract_definition",
name: ContractDefinitionTemplate.DEFAULT,
body: "### Contract body",
appendix: "### Contract appendix",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface Repository
DTOContractDefinition
> {
getAllLanguages: () => Promise<SelectOption[]>;
getAllTemplates: () => Promise<SelectOption[]>;
}

export const ContractDefinitionRepository: Repository = class ContractDefinitionRepository {
Expand Down Expand Up @@ -88,4 +89,18 @@ export const ContractDefinitionRepository: Repository = class ContractDefinition
}));
});
}

static getAllTemplates(): Promise<SelectOption[]> {
return fetchApi(contractDefinitionRoutes.getAll(), {
method: "OPTIONS",
}).then(async (response) => {
const checkedResponse = await checkStatus(response);
const result: { value: string; display_name: string }[] =
checkedResponse.actions.POST.name.choices;
return result.map(({ value, display_name: label }) => ({
value,
label,
}));
});
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ export const getContractDefinitionScenarioStore = () => {
(item) => item.id === contractDefinitionToUpdate?.id,
);

let newCertificationDef: ContractDefinition;
let newContractDef: ContractDefinition;
if (index >= 0) {
newCertificationDef = { ...list[index], ...payload };
list[index] = newCertificationDef;
newContractDef = { ...list[index], ...payload };
list[index] = newContractDef;
} else {
newCertificationDef = { id: faker.string.uuid(), ...payload };
list.push(newCertificationDef);
newContractDef = { id: faker.string.uuid(), ...payload };
list.push(newContractDef);
}

return newCertificationDef;
return newContractDef;
};
return {
list,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ test.describe("Contract definition form", () => {
await expect(page.getByRole("option", { name: "English" })).toHaveCount(1);
await expect(page.getByRole("option", { name: "French" })).toHaveCount(1);
await page.getByRole("option", { name: "French" }).click();
await page.getByTestId("contract-definition-template-name-input").click();
await expect(
page.getByRole("option", { name: "Contract Definition Default" }),
).toHaveCount(1);
await expect(
page.getByRole("option", { name: "Contract Definition Unicamp" }),
).toHaveCount(1);
await page
.getByRole("option", { name: "Contract Definition Default" })
.click();
await expect(page.getByLabel("Description")).toHaveCount(1);
await expect(page.getByRole("heading", { name: "Body" })).toBeVisible();
await expect(
Expand All @@ -75,6 +85,10 @@ test.describe("Contract definition form", () => {
await page.getByLabel("Title", { exact: true }).fill("Contract title");
await page.getByLabel("Language").click();
await page.getByRole("option", { name: "English" }).click();
await page.getByLabel("Template name").click();
await page
.getByRole("option", { name: "Contract Definition Default" })
.click();
await page.getByLabel("Description").click();
await page.getByLabel("Description").fill("Contract description");
const MdEditorBody = page
Expand Down Expand Up @@ -117,6 +131,10 @@ test.describe("Contract definition form", () => {
page.getByText("description is a required field"),
"Mui-error",
);
await expectHaveClasses(
page.getByText("name is a required field"),
"Mui-error",
);
await expectHaveClasses(
page.getByText("body is a required field"),
"Mui-error",
Expand Down Expand Up @@ -181,7 +199,10 @@ test.describe("Contract definition form", () => {
contractDefinition.language,
);
await expect(page.getByLabel("Description", { exact: true })).toHaveValue(
contractDefinition.description ?? "",
contractDefinition.description,
);
await expect(page.locator("[name='name']")).toHaveValue(
contractDefinition.name,
);

await expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ export const CONTRACT_DEFINITION_OPTIONS_REQUEST_RESULT = {
},
],
},
name: {
choices: [
{
value: "contract_definition_default",
display_name: "Contract Definition Default",
},
{
value: "contract_definition_unicamp",
display_name: "Contract Definition Unicamp",
},
],
},
},
},
};
6 changes: 6 additions & 0 deletions src/frontend/admin/src/tests/product/product.test.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from "@playwright/test";
import { faker } from "@faker-js/faker";
import { mockPlaywrightCrud } from "../useResourceHandler";
import { CONTRACT_DEFINITION_OPTIONS_REQUEST_RESULT } from "../mocks/contract-definitions/contract-definition-mocks";
import {
getProductScenarioStore,
mockTargetCourses,
Expand Down Expand Up @@ -193,6 +194,7 @@ test.describe("Product form", () => {
data: store.contractsDefinitions,
routeUrl: "http://localhost:8071/api/v1.0/admin/contract-definitions/",
page,
optionsResult: CONTRACT_DEFINITION_OPTIONS_REQUEST_RESULT,
createCallback: (payload) => {
const contract: ContractDefinition = {
...payload,
Expand All @@ -217,6 +219,10 @@ test.describe("Product form", () => {
await page
.getByLabel("Description", { exact: true })
.fill("Test contract desc");
await page.getByLabel("Template name", { exact: true }).click();
await page
.getByRole("option", { name: "Contract Definition Default" })
.click();
const MdEditorBody = page
.getByLabel("Add a contract definition")
.getByTestId("md-editor-body")
Expand Down
4 changes: 3 additions & 1 deletion src/frontend/admin/src/utils/string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const removeEOL = (str?: string): string => {
import { Maybe, Nullable } from "@/types/utils";

export const removeEOL = (str: Maybe<Nullable<string>>): string => {
if (!str) {
return "";
}
Expand Down

0 comments on commit a7151b3

Please sign in to comment.