Skip to content

Commit 2a7f3fa

Browse files
feat: make campaign deadlines configurable via database (#3140)
1 parent 2eb9b06 commit 2a7f3fa

31 files changed

Lines changed: 316 additions & 99 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Configurable campaign deadlines per declaration year
2+
CREATE TABLE IF NOT EXISTS "app_campaign_deadline" (
3+
"year" integer PRIMARY KEY NOT NULL,
4+
"decl1_modification_deadline" date NOT NULL,
5+
"decl1_justification_deadline" date NOT NULL,
6+
"decl1_joint_evaluation_deadline" date NOT NULL,
7+
"decl2_modification_deadline" date NOT NULL,
8+
"decl2_justification_deadline" date NOT NULL,
9+
"decl2_joint_evaluation_deadline" date NOT NULL
10+
);

packages/app/drizzle/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@
148148
"when": 1775200000000,
149149
"tag": "0020_clean-user-columns",
150150
"breakpoints": true
151+
},
152+
{
153+
"idx": 21,
154+
"version": "7",
155+
"when": 1775400000000,
156+
"tag": "0021_add-campaign-deadlines",
157+
"breakpoints": true
151158
}
152159
]
153160
}

packages/app/src/app/avis-cse/etape/[step]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
TOTAL_STEPS,
77
} from "~/modules/cseOpinion";
88
import { auth } from "~/server/auth";
9+
import { getCampaignDeadlines } from "~/server/db/getCampaignDeadlines";
910
import { api } from "~/trpc/server";
1011

1112
type StepPageProps = {
@@ -29,10 +30,13 @@ export default async function CseOpinionStepPage({ params }: StepPageProps) {
2930
const initialData = mapOpinionsFromDb(opinions);
3031
const hasSecondDeclaration =
3132
declarationData.declaration.secondDeclarationStatus === "submitted";
33+
const campaignDeadlines = await getCampaignDeadlines(
34+
declarationData.declaration.year,
35+
);
3236
return (
3337
<Step1Opinions
3438
compliancePath={declarationData.declaration.compliancePath}
35-
declarationYear={declarationData.declaration.year}
39+
cseDeadline={campaignDeadlines.decl2JointEvaluationDeadline}
3640
email={session?.user?.email ?? undefined}
3741
hasSecondDeclaration={hasSecondDeclaration}
3842
initialData={initialData}

packages/app/src/modules/cseOpinion/Step1Opinions.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import formStyles from "./shared/formActions.module.scss";
1616
import type { OpinionType } from "./types";
1717

1818
type Props = {
19+
cseDeadline: Date;
1920
initialData?: {
2021
firstDeclAccuracyOpinion: OpinionType | null;
2122
firstDeclAccuracyDate: string | null;
@@ -28,15 +29,14 @@ type Props = {
2829
secondDeclGapOpinion: OpinionType | null;
2930
secondDeclGapDate: string | null;
3031
};
31-
declarationYear: number;
3232
email?: string;
3333
compliancePath?: string | null;
3434
hasSecondDeclaration?: boolean;
3535
};
3636

3737
export function Step1Opinions({
38+
cseDeadline,
3839
initialData,
39-
declarationYear,
4040
email,
4141
compliancePath,
4242
hasSecondDeclaration = true,
@@ -122,7 +122,7 @@ export function Step1Opinions({
122122

123123
{isJointEvaluation && (
124124
<SubmissionBanner
125-
deadline={`1er février ${declarationYear}`}
125+
deadline={cseDeadline}
126126
email={email ?? "adresse@exemple.fr"}
127127
/>
128128
)}

packages/app/src/modules/cseOpinion/__tests__/Step1Opinions.test.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
44
import { Step1Opinions } from "../Step1Opinions";
55

66
const mockPush = vi.fn();
7+
const cseDeadline = new Date("2028-02-01T00:00:00");
78

89
vi.mock("next/navigation", async () => ({
910
...(await vi.importActual("next/navigation")),
@@ -44,7 +45,7 @@ describe("Step1Opinions", () => {
4445
render(
4546
<Step1Opinions
4647
compliancePath="joint_evaluation"
47-
declarationYear={2025}
48+
cseDeadline={cseDeadline}
4849
/>,
4950
);
5051

@@ -56,7 +57,9 @@ describe("Step1Opinions", () => {
5657
});
5758

5859
it("does not render compliance path title for other paths", () => {
59-
render(<Step1Opinions compliancePath="justify" declarationYear={2025} />);
60+
render(
61+
<Step1Opinions compliancePath="justify" cseDeadline={cseDeadline} />,
62+
);
6063

6164
expect(
6265
screen.queryByText(
@@ -66,21 +69,21 @@ describe("Step1Opinions", () => {
6669
});
6770

6871
it("renders h1 as CSE opinion title when no compliance path banner", () => {
69-
render(<Step1Opinions declarationYear={2025} />);
72+
render(<Step1Opinions cseDeadline={cseDeadline} />);
7073

7174
const heading = screen.getByRole("heading", { level: 1 });
7275
expect(heading).toHaveTextContent("Transmettre l'avis ou les avis du CSE");
7376
});
7477

7578
it("renders the stepper at step 1", () => {
76-
render(<Step1Opinions declarationYear={2025} />);
79+
render(<Step1Opinions cseDeadline={cseDeadline} />);
7780

7881
expect(screen.getByText(/Étape 1 sur 2/)).toBeInTheDocument();
7982
});
8083

8184
it("renders both declaration sections when hasSecondDeclaration is true", () => {
8285
render(
83-
<Step1Opinions declarationYear={2025} hasSecondDeclaration={true} />,
86+
<Step1Opinions cseDeadline={cseDeadline} hasSecondDeclaration={true} />,
8487
);
8588

8689
expect(screen.getByText("Première déclaration")).toBeInTheDocument();
@@ -89,7 +92,7 @@ describe("Step1Opinions", () => {
8992

9093
it("hides second declaration section when hasSecondDeclaration is false", () => {
9194
render(
92-
<Step1Opinions declarationYear={2025} hasSecondDeclaration={false} />,
95+
<Step1Opinions cseDeadline={cseDeadline} hasSecondDeclaration={false} />,
9396
);
9497

9598
expect(screen.getByText("Première déclaration")).toBeInTheDocument();
@@ -100,7 +103,7 @@ describe("Step1Opinions", () => {
100103
render(
101104
<Step1Opinions
102105
compliancePath="joint_evaluation"
103-
declarationYear={2025}
106+
cseDeadline={cseDeadline}
104107
/>,
105108
);
106109

@@ -112,7 +115,7 @@ describe("Step1Opinions", () => {
112115
});
113116

114117
it("does not render the submission banner for other paths", () => {
115-
render(<Step1Opinions declarationYear={2025} />);
118+
render(<Step1Opinions cseDeadline={cseDeadline} />);
116119

117120
expect(
118121
screen.queryByText(
@@ -122,7 +125,7 @@ describe("Step1Opinions", () => {
122125
});
123126

124127
it("renders previous and next buttons", () => {
125-
render(<Step1Opinions declarationYear={2025} />);
128+
render(<Step1Opinions cseDeadline={cseDeadline} />);
126129

127130
expect(
128131
screen.getByRole("button", { name: /Précédent/ }),
@@ -132,7 +135,7 @@ describe("Step1Opinions", () => {
132135

133136
it("shows validation error when submitting empty form", async () => {
134137
const user = userEvent.setup();
135-
render(<Step1Opinions declarationYear={2025} />);
138+
render(<Step1Opinions cseDeadline={cseDeadline} />);
136139

137140
await user.click(screen.getByRole("button", { name: /Suivant/ }));
138141

@@ -146,7 +149,7 @@ describe("Step1Opinions", () => {
146149
const user = userEvent.setup();
147150
render(
148151
<Step1Opinions
149-
declarationYear={2025}
152+
cseDeadline={cseDeadline}
150153
hasSecondDeclaration={true}
151154
initialData={{
152155
firstDeclAccuracyOpinion: "favorable",
@@ -188,7 +191,7 @@ describe("Step1Opinions", () => {
188191
const user = userEvent.setup();
189192
render(
190193
<Step1Opinions
191-
declarationYear={2025}
194+
cseDeadline={cseDeadline}
192195
hasSecondDeclaration={false}
193196
initialData={{
194197
firstDeclAccuracyOpinion: "favorable",
@@ -223,7 +226,7 @@ describe("Step1Opinions", () => {
223226
it("renders with initial data pre-filled", () => {
224227
render(
225228
<Step1Opinions
226-
declarationYear={2025}
229+
cseDeadline={cseDeadline}
227230
initialData={{
228231
firstDeclAccuracyOpinion: "favorable",
229232
firstDeclAccuracyDate: "2026-01-15",
@@ -252,7 +255,7 @@ describe("Step1Opinions", () => {
252255
render(
253256
<Step1Opinions
254257
compliancePath="joint_evaluation"
255-
declarationYear={2025}
258+
cseDeadline={cseDeadline}
256259
email="test@example.fr"
257260
/>,
258261
);
@@ -264,7 +267,7 @@ describe("Step1Opinions", () => {
264267
render(
265268
<Step1Opinions
266269
compliancePath="joint_evaluation"
267-
declarationYear={2025}
270+
cseDeadline={cseDeadline}
268271
/>,
269272
);
270273

packages/app/src/modules/cseOpinion/components/SubmissionBanner.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { formatLongDate } from "~/modules/domain";
12
import styles from "./SubmissionBanner.module.scss";
23

34
type Props = {
45
email: string;
5-
deadline: string;
6+
deadline: Date;
67
};
78

89
export function SubmissionBanner({ email, deadline }: Props) {
@@ -24,7 +25,7 @@ export function SubmissionBanner({ email, deadline }: Props) {
2425
</p>
2526
<p className="fr-mb-0">
2627
Vous pouvez modifier votre dépôt jusqu'au{" "}
27-
<strong>{deadline}</strong>
28+
<strong>{formatLongDate(deadline)}</strong>
2829
</p>
2930
</div>
3031
</div>

0 commit comments

Comments
 (0)