Skip to content

Commit 76e9eb7

Browse files
feat: button migration from product to form
1 parent e02889c commit 76e9eb7

File tree

12 files changed

+138
-97
lines changed

12 files changed

+138
-97
lines changed

webapp-backoffice/prisma/migrations/20250318092552_add_root_form_template_and_forms/migration.sql

-28
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
-- Step 1: Create the form template if it doesn't exist
2+
DO $$
3+
BEGIN
4+
IF NOT EXISTS (SELECT 1 FROM "FormTemplate" WHERE slug = 'root') THEN
5+
INSERT INTO "FormTemplate" (title, slug, active, created_at, updated_at)
6+
VALUES ('Formulaire des démarches', 'root', true, NOW(), NOW());
7+
END IF;
8+
END $$;
9+
10+
-- Step 2: Get the ID of the root form template and create forms for each product
11+
DO $$
12+
DECLARE
13+
root_template_id INTEGER;
14+
BEGIN
15+
-- Get the ID of the root form template
16+
SELECT id INTO root_template_id FROM "FormTemplate" WHERE slug = 'root';
17+
18+
-- Create a form for each product if it doesn't already have one linked to the root template
19+
INSERT INTO "Form" (product_id, form_template_id, created_at, updated_at)
20+
SELECT p.id, root_template_id, NOW(), NOW()
21+
FROM "Product" p
22+
WHERE NOT EXISTS (
23+
SELECT 1
24+
FROM "Form" f
25+
WHERE f.product_id = p.id
26+
AND f.form_template_id = root_template_id
27+
);
28+
END $$;
29+
30+
-- Step 3: Add form_id column to Button table without NOT NULL constraint initially
31+
ALTER TABLE "Button" ADD COLUMN "form_id" INTEGER;
32+
33+
-- Step 4: Update the form_id for all buttons based on their product_id
34+
UPDATE "Button" b
35+
SET form_id = f.id
36+
FROM "Form" f
37+
JOIN "Product" p ON f.product_id = p.id
38+
WHERE b.product_id = p.id;
39+
40+
-- Step 5: Add foreign key constraint for form_id
41+
ALTER TABLE "Button"
42+
ADD CONSTRAINT "Button_form_id_fkey"
43+
FOREIGN KEY ("form_id") REFERENCES "Form"("id")
44+
ON DELETE RESTRICT ON UPDATE CASCADE;
45+
46+
-- Step 6: Make form_id NOT NULL after populating it
47+
ALTER TABLE "Button" ALTER COLUMN "form_id" SET NOT NULL;
48+
49+
-- Step 7: Drop the foreign key constraint for product_id
50+
ALTER TABLE "Button" DROP CONSTRAINT "Button_product_id_fkey";
51+
52+
-- Step 8: Drop the product_id column
53+
ALTER TABLE "Button" DROP COLUMN "product_id";

webapp-backoffice/prisma/schema.prisma

+3-3
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ model Button {
265265
description String?
266266
xwiki_title String?
267267
isTest Boolean? @default(false)
268-
product Product? @relation(fields: [product_id], references: [id])
269-
product_id Int
268+
form Form @relation(fields: [form_id], references: [id])
269+
form_id Int
270270
271271
reviews Review[]
272272
@@ -308,7 +308,6 @@ model Product {
308308
urls String[]
309309
volume Int?
310310
xwiki_id Int? @unique
311-
buttons Button[]
312311
accessRights AccessRight[]
313312
forms Form[]
314313
favorites Favorite[]
@@ -523,6 +522,7 @@ model Form {
523522
product Product @relation(fields: [product_id], references: [id])
524523
product_id Int
525524
form_configs FormConfig[]
525+
buttons Button[]
526526
created_at DateTime @default(now())
527527
updated_at DateTime @default(now())
528528
}

webapp-backoffice/prisma/seed.ts

+4-11
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async function seed_users_products() {
6060
const promisesProducts: Promise<Product>[] = [];
6161
const promisesWLDs: Promise<WhiteListedDomain>[] = [];
6262

63-
await prisma.formTemplate.upsert({
63+
const rootFormTemplate = await prisma.formTemplate.upsert({
6464
where: { slug: 'root' },
6565
update: {},
6666
create: {
@@ -110,12 +110,6 @@ async function seed_users_products() {
110110
name: randomEntity.name
111111
}
112112
},
113-
buttons: {
114-
create: buttons.map(b => ({
115-
...b,
116-
product_id: b.product_id
117-
})) as Button[]
118-
},
119113
accessRights: {
120114
create: {
121115
user_email: users.filter(u => u.active && u?.role !== 'admin')[
@@ -127,10 +121,9 @@ async function seed_users_products() {
127121
forms: {
128122
create: [
129123
{
130-
form_template: {
131-
connect: {
132-
slug: 'root'
133-
}
124+
form_template_id: rootFormTemplate.id,
125+
buttons: {
126+
create: buttons as Button[]
134127
}
135128
}
136129
]

webapp-backoffice/src/components/dashboard/Product/ProductCard.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ProductWithButtons } from '@/src/types/prismaTypesExtended';
1+
import { ProductWithForms } from '@/src/types/prismaTypesExtended';
22
import { getIntentionFromAverage } from '@/src/utils/stats';
33
import {
44
formatNumberWithSpaces,
@@ -65,7 +65,7 @@ const ProductCard = ({
6565
onDeleteProduct,
6666
onDeleteEssential
6767
}: {
68-
product: ProductWithButtons;
68+
product: ProductWithForms;
6969

7070
userId: number;
7171
entity: Entity;
@@ -591,7 +591,7 @@ const ProductCard = ({
591591
width={'full'}
592592
height={50}
593593
/>
594-
) : (product.buttons.length > 0 &&
594+
) : (product.forms[0]?.buttons.length > 0 &&
595595
nbReviews &&
596596
nbReviews > 0) ||
597597
session?.user.role.includes('admin') ? (
@@ -724,7 +724,7 @@ const ProductCard = ({
724724
</div>
725725
)}
726726
</div>
727-
) : product.buttons.length === 0 ? (
727+
) : product.forms[0]?.buttons.length === 0 ? (
728728
<NoButtonsPanel onButtonClick={handleButtonClick} />
729729
) : (
730730
<NoReviewsPanel

webapp-backoffice/src/components/dashboard/ProductButton/ButtonModal.tsx

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1+
import { ButtonWithForm } from '@/src/types/prismaTypesExtended';
2+
import { trpc } from '@/src/utils/trpc';
13
import { fr } from '@codegouvfr/react-dsfr';
4+
import Button from '@codegouvfr/react-dsfr/Button';
5+
import { Checkbox } from '@codegouvfr/react-dsfr/Checkbox';
6+
import { Input } from '@codegouvfr/react-dsfr/Input';
27
import { ModalProps } from '@codegouvfr/react-dsfr/Modal';
38
import { RadioButtons } from '@codegouvfr/react-dsfr/RadioButtons';
4-
import { Input } from '@codegouvfr/react-dsfr/Input';
5-
import { Accordion } from '@codegouvfr/react-dsfr/Accordion';
69
import { Button as PrismaButtonType } from '@prisma/client';
10+
import { push } from '@socialgouv/matomo-next';
11+
import Image from 'next/image';
712
import React from 'react';
813
import { tss } from 'tss-react/dsfr';
9-
import { Checkbox } from '@codegouvfr/react-dsfr/Checkbox';
10-
import Image from 'next/image';
11-
import { trpc } from '@/src/utils/trpc';
12-
import Button from '@codegouvfr/react-dsfr/Button';
13-
import { push } from '@socialgouv/matomo-next';
1414

1515
interface CustomModalProps {
1616
buttonProps: {
@@ -29,16 +29,16 @@ interface Props {
2929
isOpen: boolean;
3030
modal: CustomModalProps;
3131
modalType: string;
32-
button?: PrismaButtonType | null;
32+
button?: ButtonWithForm | null;
3333
onButtonCreatedOrUpdated: (isTest: boolean) => void;
34-
product_id: number;
34+
form_id: number;
3535
}
3636

3737
const defaultButton = {
3838
title: '',
3939
description: '',
4040
xwiki_title: null,
41-
product_id: -1,
41+
form_id: -1,
4242
isTest: false
4343
};
4444

@@ -126,7 +126,7 @@ const ButtonModal = (props: Props) => {
126126
return;
127127
}
128128

129-
currentButton.product_id = props.product_id;
129+
currentButton.form_id = props.form_id;
130130

131131
if ('id' in currentButton) {
132132
updateButton.mutate(currentButton);
@@ -135,11 +135,11 @@ const ButtonModal = (props: Props) => {
135135
}
136136
};
137137

138-
const buttonCodeClair = `<a href="https://jedonnemonavis.numerique.gouv.fr/Demarches/${button?.product_id}?button=${button?.id}" target='_blank' title="Je donne mon avis - nouvelle fenêtre">
138+
const buttonCodeClair = `<a href="https://jedonnemonavis.numerique.gouv.fr/Demarches/${button?.form.product_id}?button=${button?.id}" target='_blank' title="Je donne mon avis - nouvelle fenêtre">
139139
<img src="https://jedonnemonavis.numerique.gouv.fr/static/bouton-${buttonColor}-clair.svg" alt="Je donne mon avis" />
140140
</a>`;
141141

142-
const buttonCodeSombre = `<a href="https://jedonnemonavis.numerique.gouv.fr/Demarches/${button?.product_id}?button=${button?.id}" target='_blank' title="Je donne mon avis - nouvelle fenêtre">
142+
const buttonCodeSombre = `<a href="https://jedonnemonavis.numerique.gouv.fr/Demarches/${button?.form.product_id}?button=${button?.id}" target='_blank' title="Je donne mon avis - nouvelle fenêtre">
143143
<img src="https://jedonnemonavis.numerique.gouv.fr/static/bouton-${buttonColor}-sombre.svg" alt="Je donne mon avis" />
144144
</a>`;
145145

webapp-backoffice/src/components/dashboard/ProductButton/ProductButtonCard.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { formatDateToFrenchString } from '@/src/utils/tools';
1+
import { ButtonWithForm } from '@/src/types/prismaTypesExtended';
22
import { fr } from '@codegouvfr/react-dsfr';
33
import Button from '@codegouvfr/react-dsfr/Button';
44
import { Tag } from '@codegouvfr/react-dsfr/Tag';
5-
import { Button as PrismaButtonType, RightAccessStatus } from '@prisma/client';
6-
import React from 'react';
75
import { Menu, MenuItem } from '@mui/material';
8-
import { tss } from 'tss-react/dsfr';
6+
import { RightAccessStatus } from '@prisma/client';
97
import { push } from '@socialgouv/matomo-next';
8+
import React from 'react';
9+
import { tss } from 'tss-react/dsfr';
1010

1111
interface Props {
12-
button: PrismaButtonType;
13-
onButtonClick: (modalType: string, button?: PrismaButtonType) => void;
12+
button: ButtonWithForm;
13+
onButtonClick: (modalType: string, button?: ButtonWithForm) => void;
1414
ownRight: Exclude<RightAccessStatus, 'removed'>;
1515
}
1616

@@ -106,7 +106,7 @@ const ProductButtonCard = (props: Props) => {
106106
<MenuItem
107107
onClick={() => {
108108
navigator.clipboard.writeText(
109-
`https://jedonnemonavis.numerique.gouv.fr/Demarches/${button.product_id}?button=${button.id}`
109+
`https://jedonnemonavis.numerique.gouv.fr/Demarches/${button.form.product_id}?button=${button.id}`
110110
);
111111
handleClose();
112112
}}

webapp-backoffice/src/pages/administration/dashboard/product/[id]/forms.tsx

+17-20
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,31 @@ import ProductButtonCard from '@/src/components/dashboard/ProductButton/ProductB
22
import ProductLayout from '@/src/layouts/Product/ProductLayout';
33
import { fr } from '@codegouvfr/react-dsfr';
44
import Button from '@codegouvfr/react-dsfr/Button';
5-
import Checkbox from '@codegouvfr/react-dsfr/Checkbox';
6-
import {
7-
Button as PrismaButtonType,
8-
Product,
9-
RightAccessStatus
10-
} from '@prisma/client';
5+
import { RightAccessStatus } from '@prisma/client';
116
import { tss } from 'tss-react/dsfr';
127
import { getServerSideProps } from '.';
138
import { Pagination } from '../../../../../components/ui/Pagination';
149

10+
import NoButtonsPanel from '@/src/components/dashboard/Pannels/NoButtonsPanel';
11+
import ProductFormConfigurationInfo from '@/src/components/dashboard/Product/ProductFormConfigurationInfo';
1512
import ButtonModal from '@/src/components/dashboard/ProductButton/ButtonModal';
1613
import { Loader } from '@/src/components/ui/Loader';
14+
import { useFilters } from '@/src/contexts/FiltersContext';
15+
import {
16+
ButtonWithForm,
17+
ProductWithForms
18+
} from '@/src/types/prismaTypesExtended';
1719
import { getNbPages } from '@/src/utils/tools';
20+
import { trpc } from '@/src/utils/trpc';
1821
import { createModal } from '@codegouvfr/react-dsfr/Modal';
1922
import { useIsModalOpen } from '@codegouvfr/react-dsfr/Modal/useIsModalOpen';
20-
import React, { useEffect } from 'react';
21-
import { trpc } from '@/src/utils/trpc';
23+
import { push } from '@socialgouv/matomo-next';
2224
import Head from 'next/head';
23-
import NoButtonsPanel from '@/src/components/dashboard/Pannels/NoButtonsPanel';
2425
import { useRouter } from 'next/router';
25-
import ProductBottomInfo from '@/src/components/dashboard/ProductButton/ProductBottomInfo';
26-
import { useFilters } from '@/src/contexts/FiltersContext';
27-
import Select from '@codegouvfr/react-dsfr/Select';
28-
import { push } from '@socialgouv/matomo-next';
29-
import ProductFormConfigurationInfo from '@/src/components/dashboard/Product/ProductFormConfigurationInfo';
26+
import React from 'react';
3027

3128
interface Props {
32-
product: Product;
29+
product: ProductWithForms;
3330
ownRight: Exclude<RightAccessStatus, 'removed'>;
3431
}
3532

@@ -46,7 +43,7 @@ const ProductButtonsPage = (props: Props) => {
4643
const [modalType, setModalType] = React.useState<string>('');
4744

4845
const [currentButton, setCurrentButton] =
49-
React.useState<PrismaButtonType | null>(null);
46+
React.useState<ButtonWithForm | null>(null);
5047
const router = useRouter();
5148

5249
const [testFilter, setTestFilter] = React.useState<boolean>(false);
@@ -63,7 +60,7 @@ const ProductButtonsPage = (props: Props) => {
6360
{
6461
numberPerPage,
6562
page: currentPage,
66-
product_id: product.id,
63+
form_id: product.forms[0].id,
6764
isTest: testFilter,
6865
filterByTitle: filters.filter
6966
},
@@ -92,7 +89,7 @@ const ProductButtonsPage = (props: Props) => {
9289

9390
const isModalOpen = useIsModalOpen(modal);
9491

95-
const handleModalOpening = (modalType: string, button?: PrismaButtonType) => {
92+
const handleModalOpening = (modalType: string, button?: ButtonWithForm) => {
9693
setCurrentButton(button ? button : null);
9794
setModalType(modalType);
9895
modal.open();
@@ -126,7 +123,7 @@ const ProductButtonsPage = (props: Props) => {
126123
/>
127124
</Head>
128125
<ButtonModal
129-
product_id={product.id}
126+
form_id={product.forms[0].id}
130127
modal={modal}
131128
isOpen={isModalOpen}
132129
modalType={modalType}
@@ -141,7 +138,7 @@ const ProductButtonsPage = (props: Props) => {
141138
<p>
142139
Vous pouvez{' '}
143140
<a
144-
href={`${process.env.NEXT_PUBLIC_FORM_APP_URL}/Demarches/${buttons[0]?.product_id}?button=${buttons[0]?.id}&iframe=true`}
141+
href={`${process.env.NEXT_PUBLIC_FORM_APP_URL}/Demarches/${buttons[0]?.form.product_id}?button=${buttons[0]?.id}&iframe=true`}
145142
target="_blank"
146143
>
147144
prévisualiser le formulaire JDMA

webapp-backoffice/src/pages/administration/dashboard/product/[id]/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export const getServerSideProps: GetServerSideProps = async context => {
1212
where: {
1313
id: parseInt(id as string),
1414
status: 'published'
15+
},
16+
include: {
17+
forms: true
1518
}
1619
});
1720

0 commit comments

Comments
 (0)