Skip to content

Commit

Permalink
feat: implement the sacem declaration form page logic
Browse files Browse the repository at this point in the history
  • Loading branch information
sneko committed Jan 2, 2025
1 parent 553746b commit a1d6c32
Show file tree
Hide file tree
Showing 16 changed files with 1,366 additions and 156 deletions.
5 changes: 5 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ const preview: Preview = {
id: 'duplicate-id-active',
selector: '*:not([id^="fr-header-quick-access-item"])',
},
{
// We use a number input with combobox and it seems non-standard, so silenting seems for our use case it's justified
id: 'aria-allowed-role',
selector: '*:not([role="combobox"][type="number"])',
},
],
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ComponentProps, StoryHelperFactory } from '@ad/.storybook/helpers';
import { playFindMainTitle } from '@ad/.storybook/testing';
import { AsCollaborator as PrivateLayoutAsCollaboratorStory } from '@ad/src/app/(private)/PrivateLayout.stories';
import { SacemDeclarationPage } from '@ad/src/app/(private)/dashboard/organization/[organizationId]/serie/[eventSerieId]/declaration/sacem/SacemDeclarationPage';
import { sacemDeclarations, sacemDeclarationsWrappers } from '@ad/src/fixtures/declaration';
import { eventCategoryTickets, eventsSeries, eventsWrappers } from '@ad/src/fixtures/event';
import { getTRPCMock } from '@ad/src/server/mock/trpc';

Expand All @@ -26,13 +27,27 @@ const mswCommonParameters = [
eventSerie: eventsSeries[0],
},
}),
getTRPCMock({
type: 'query',
path: ['getSacemDeclaration'],
response: {
sacemDeclarationWrapper: sacemDeclarationsWrappers[0],
},
}),
getTRPCMock({
type: 'mutation',
path: ['updateEventCategoryTickets'],
response: {
eventCategoryTickets: eventCategoryTickets[0],
},
}),
getTRPCMock({
type: 'mutation',
path: ['fillSacemDeclaration'],
response: {
sacemDeclaration: sacemDeclarations[0],
},
}),
];

const commonComponentProps: ComponentProps<ComponentType> = {
Expand Down

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions src/components/EventSalesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,30 @@ export function EventSalesTable({ wrapper, onRowUpdate }: EventSalesTableProps)
disableRowSelectionOnClick
processRowUpdate={processRowUpdate}
onProcessRowUpdateError={handleProcessRowUpdateError}
onCellEditStart={(params, event) => {
// [WORKAROUND] As for number inputs outside the datagrid, there is the native issue of scrolling making the value changing
// So doing the same workaround to prevent scrolling when it is focused
// Ref: https://github.com/mui/material-ui/issues/19154#issuecomment-2566529204
const editCellElement = event.target as HTMLDivElement;

// At the time of the callback the input child is not yet created, so we have to wait for it
// Note: it appears in a few cases it does not work the first time... maybe we should delay a bit the "observe"?
const observer = new MutationObserver(() => {
const editCellInputElement = editCellElement.querySelector('input[type="number"]');

if (editCellInputElement) {
observer.disconnect();

// Note: no need to remove the listener since it will after the focus is released due to the cell input element being deleted by `DataGrid`
editCellInputElement.addEventListener('wheel', (event) => {
(event.target as HTMLInputElement).blur();
});
}
});

// Start observing the cell element for child additions
observer.observe(editCellElement, { childList: true, subtree: true });
}}
aria-label="tableau des ventes d'une représentation"
data-sentry-mask
/>
Expand Down
125 changes: 125 additions & 0 deletions src/fixtures/declaration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { eventsSeries } from '@ad/src/fixtures/event';
import { organizations } from '@ad/src/fixtures/organization';
import {
SacemDeclarationSchema,
SacemDeclarationSchemaType,
SacemDeclarationWrapperSchema,
SacemDeclarationWrapperSchemaType,
} from '@ad/src/models/entities/declaration';

export const sacemDeclarations: SacemDeclarationSchemaType[] = [
SacemDeclarationSchema.parse({
id: 'd79cb3ba-745e-5d9a-8903-4a02327a7e01',
eventSerieId: eventsSeries[0].id,
clientId: '23434',
placeName: 'Salle Hermione',
placeCapacity: 230,
managerName: 'Jean Derrien',
managerTitle: 'Directeur',
organizationName: organizations[0].name,
eventSerieName: eventsSeries[0].name,
eventSerieStartAt: eventsSeries[0].startAt,
eventSerieEndAt: eventsSeries[0].endAt,
eventsCount: 4,
paidTickets: 421,
freeTickets: 53,
includingTaxesAmount: 8883.1,
excludingTaxesAmount: 8420,
}),
SacemDeclarationSchema.parse({
id: 'd79cb3ba-745e-5d9a-8903-4a02327a7e02',
eventSerieId: eventsSeries[1].id,
clientId: '91384',
placeName: 'Agora',
placeCapacity: 40,
managerName: 'John Doe',
managerTitle: 'Gérant',
organizationName: organizations[1].name,
eventSerieName: eventsSeries[1].name,
eventSerieStartAt: eventsSeries[1].startAt,
eventSerieEndAt: eventsSeries[1].endAt,
eventsCount: 1,
paidTickets: 35,
freeTickets: 5,
includingTaxesAmount: 422,
excludingTaxesAmount: 400,
}),
SacemDeclarationSchema.parse({
id: 'd79cb3ba-745e-5d9a-8903-4a02327a7e03',
eventSerieId: eventsSeries[2].id,
clientId: '12947',
placeName: 'Grande salle',
placeCapacity: 140,
managerName: 'Alex Terrieur',
managerTitle: 'Comptable',
organizationName: organizations[2].name,
eventSerieName: eventsSeries[2].name,
eventSerieStartAt: eventsSeries[2].startAt,
eventSerieEndAt: eventsSeries[2].endAt,
eventsCount: 2,
paidTickets: 204,
freeTickets: 35,
includingTaxesAmount: 7385,
excludingTaxesAmount: 7000,
}),
];

export const sacemDeclarationsWrappers: SacemDeclarationWrapperSchemaType[] = [
SacemDeclarationWrapperSchema.parse({
declaration: sacemDeclarations[0],
placeholder: {
clientId: [sacemDeclarations[0].clientId, sacemDeclarations[1].clientId],
placeName: [sacemDeclarations[1].placeName],
placeCapacity: [sacemDeclarations[0].placeCapacity, sacemDeclarations[2].placeCapacity],
managerName: [sacemDeclarations[2].managerName],
managerTitle: [sacemDeclarations[1].managerTitle, sacemDeclarations[2].managerTitle],
organizationName: sacemDeclarations[0].organizationName,
eventSerieName: sacemDeclarations[0].eventSerieName,
eventSerieStartAt: sacemDeclarations[0].eventSerieStartAt,
eventSerieEndAt: sacemDeclarations[0].eventSerieEndAt,
eventsCount: sacemDeclarations[0].eventsCount,
paidTickets: sacemDeclarations[0].paidTickets,
freeTickets: sacemDeclarations[0].freeTickets,
includingTaxesAmount: sacemDeclarations[0].includingTaxesAmount,
excludingTaxesAmount: sacemDeclarations[0].excludingTaxesAmount,
},
}),
SacemDeclarationWrapperSchema.parse({
declaration: sacemDeclarations[1],
placeholder: {
clientId: [sacemDeclarations[0].clientId, sacemDeclarations[1].clientId],
placeName: [sacemDeclarations[1].placeName],
placeCapacity: [sacemDeclarations[0].placeCapacity, sacemDeclarations[2].placeCapacity],
managerName: [sacemDeclarations[2].managerName],
managerTitle: [sacemDeclarations[1].managerTitle, sacemDeclarations[2].managerTitle],
organizationName: sacemDeclarations[1].organizationName,
eventSerieName: sacemDeclarations[1].eventSerieName,
eventSerieStartAt: sacemDeclarations[1].eventSerieStartAt,
eventSerieEndAt: sacemDeclarations[1].eventSerieEndAt,
eventsCount: sacemDeclarations[1].eventsCount,
paidTickets: sacemDeclarations[1].paidTickets,
freeTickets: sacemDeclarations[1].freeTickets,
includingTaxesAmount: sacemDeclarations[1].includingTaxesAmount,
excludingTaxesAmount: sacemDeclarations[1].excludingTaxesAmount,
},
}),
SacemDeclarationWrapperSchema.parse({
declaration: sacemDeclarations[2],
placeholder: {
clientId: [sacemDeclarations[0].clientId, sacemDeclarations[1].clientId],
placeName: [sacemDeclarations[1].placeName],
placeCapacity: [sacemDeclarations[0].placeCapacity, sacemDeclarations[2].placeCapacity],
managerName: [sacemDeclarations[2].managerName],
managerTitle: [sacemDeclarations[1].managerTitle, sacemDeclarations[2].managerTitle],
organizationName: sacemDeclarations[2].organizationName,
eventSerieName: sacemDeclarations[2].eventSerieName,
eventSerieStartAt: sacemDeclarations[2].eventSerieStartAt,
eventSerieEndAt: sacemDeclarations[2].eventSerieEndAt,
eventsCount: sacemDeclarations[2].eventsCount,
paidTickets: sacemDeclarations[2].paidTickets,
freeTickets: sacemDeclarations[2].freeTickets,
includingTaxesAmount: sacemDeclarations[2].includingTaxesAmount,
excludingTaxesAmount: sacemDeclarations[2].excludingTaxesAmount,
},
}),
];
2 changes: 1 addition & 1 deletion src/fixtures/event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DeclarationStatusSchema, DeclarationTypeSchema } from '@ad/src/models/entities/declaration';
import { DeclarationStatusSchema, DeclarationTypeSchema } from '@ad/src/models/entities/common';
import {
EventCategoryTicketsSchema,
EventCategoryTicketsSchemaType,
Expand Down
16 changes: 16 additions & 0 deletions src/i18n/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"unexpectedError": "une erreur inattendue est survenue, veuillez retenter"
},
"validation": {
"clientId": {
"too_small_one": "le numéro client est requis"
},
"currentPassword": {
"too_small_one": "le mot de passe actuel est requis"
},
Expand All @@ -46,6 +49,12 @@
"lastname": {
"too_small_one": "le nom de famille est requis"
},
"managerName": {
"too_small_one": "le nom de la personne en charge est requis"
},
"managerTitle": {
"too_small_one": "le rôle de la personne en charge est requis"
},
"message": {
"too_big": "le message doit faire {{maximum}} caractères maximum",
"too_small_one": "le message est requis"
Expand All @@ -64,6 +73,13 @@
"passwordRequiresLowerAndUpperCharacters": "le mot de passe doit contenir au moins une majuscule et une minuscule",
"too_small_one": "le mot de passe est requis"
},
"placeCapacity": {
"invalid_type": "la capacité de la salle doit être un nombre entier",
"too_small_one": "la capacité de la salle est requise"
},
"placeName": {
"too_small_one": "le nom de la salle est requis"
},
"priceOverride": {
"too_small": "le prix doit être supérieur à {{minimum}} euro"
},
Expand Down
28 changes: 28 additions & 0 deletions src/models/actions/declaration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import z from 'zod';

import { SacemDeclarationSchema } from '@ad/src/models/entities/declaration';

export const GetSacemDeclarationSchema = z
.object({
eventSerieId: SacemDeclarationSchema.shape.id,
})
.strict();
export type GetSacemDeclarationSchemaType = z.infer<typeof GetSacemDeclarationSchema>;

export const GetSacemDeclarationPrefillSchema = GetSacemDeclarationSchema.deepPartial();
export type GetSacemDeclarationPrefillSchemaType = z.infer<typeof GetSacemDeclarationPrefillSchema>;

export const FillSacemDeclarationSchema = z
.object({
eventSerieId: SacemDeclarationSchema.shape.id,
clientId: SacemDeclarationSchema.shape.clientId,
placeName: SacemDeclarationSchema.shape.placeName,
placeCapacity: SacemDeclarationSchema.shape.placeCapacity,
managerName: SacemDeclarationSchema.shape.managerName,
managerTitle: SacemDeclarationSchema.shape.managerTitle,
})
.strict();
export type FillSacemDeclarationSchemaType = z.infer<typeof FillSacemDeclarationSchema>;

export const FillSacemDeclarationPrefillSchema = FillSacemDeclarationSchema.deepPartial();
export type FillSacemDeclarationPrefillSchemaType = z.infer<typeof FillSacemDeclarationPrefillSchema>;
7 changes: 7 additions & 0 deletions src/models/entities/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from 'zod';

export const DeclarationTypeSchema = z.enum(['SACEM']);
export type DeclarationTypeSchemaType = z.infer<typeof DeclarationTypeSchema>;

export const DeclarationStatusSchema = z.enum(['PENDING', 'PROCESSED', 'CANCELED']);
export type DeclarationStatusSchemaType = z.infer<typeof DeclarationStatusSchema>;
67 changes: 61 additions & 6 deletions src/models/entities/declaration.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,74 @@
import { z } from 'zod';

import { DeclarationStatusSchema } from '@ad/src/models/entities/common';
import { EventSerieSchema } from '@ad/src/models/entities/event';
import { OrganizationSchema } from '@ad/src/models/entities/organization';
import { applyTypedParsers } from '@ad/src/utils/zod';

export const DeclarationTypeSchema = z.enum(['SACEM']);
export type DeclarationTypeSchemaType = z.infer<typeof DeclarationTypeSchema>;

export const DeclarationStatusSchema = z.enum(['PENDING', 'PROCESSED', 'CANCELED']);
export type DeclarationStatusSchemaType = z.infer<typeof DeclarationStatusSchema>;

export const DeclarationSchema = applyTypedParsers(
z
.object({
id: z.string().uuid(),
eventSerieId: z.string().uuid(),
status: DeclarationStatusSchema,
})
.strict()
);
export type DeclarationSchemaType = z.infer<typeof DeclarationSchema>;

export const SacemDeclarationSchema = applyTypedParsers(
z
.object({
id: z.string().uuid(),
eventSerieId: z.string().uuid(),
// Settable properties
clientId: z.string().min(1).max(100),
placeName: z.string().min(1).max(150),
placeCapacity: z.number().int().nonnegative(),
managerName: z.string().min(1).max(150),
managerTitle: z.string().min(1).max(150),
// Computed properties
organizationName: OrganizationSchema.shape.name,
eventSerieName: EventSerieSchema.shape.name,
eventSerieStartAt: EventSerieSchema.shape.startAt,
eventSerieEndAt: EventSerieSchema.shape.endAt,
eventsCount: z.number().int().nonnegative(),
paidTickets: z.number().int().nonnegative(),
freeTickets: z.number().int().nonnegative(),
includingTaxesAmount: z.number().nonnegative(),
excludingTaxesAmount: z.number().nonnegative(),
})
.strict()
);
export type SacemDeclarationSchemaType = z.infer<typeof SacemDeclarationSchema>;

SacemDeclarationSchema.pick({
clientId: true,
});

export const SacemDeclarationWrapperSchema = applyTypedParsers(
z
.object({
declaration: SacemDeclarationSchema.nullable(),
// In case the declaration does not yet exist we pass to the frontend some fields for the UI to help creating the declaration
placeholder: SacemDeclarationSchema.pick({
organizationName: true,
eventSerieName: true,
eventSerieStartAt: true,
eventSerieEndAt: true,
eventsCount: true,
paidTickets: true,
freeTickets: true,
includingTaxesAmount: true,
excludingTaxesAmount: true,
}).extend({
clientId: z.array(SacemDeclarationSchema.shape.clientId),
placeName: z.array(SacemDeclarationSchema.shape.placeName),
placeCapacity: z.array(SacemDeclarationSchema.shape.placeCapacity),
managerName: z.array(SacemDeclarationSchema.shape.managerName),
managerTitle: z.array(SacemDeclarationSchema.shape.managerTitle),
}),
})
.strict()
);
export type SacemDeclarationWrapperSchemaType = z.infer<typeof SacemDeclarationWrapperSchema>;
2 changes: 1 addition & 1 deletion src/models/entities/event.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import z from 'zod';

import { DeclarationStatusSchema, DeclarationTypeSchema } from '@ad/src/models/entities/declaration';
import { DeclarationStatusSchema, DeclarationTypeSchema } from '@ad/src/models/entities/common';
import { applyTypedParsers } from '@ad/src/utils/zod';

export const LiteEventSerieSchema = applyTypedParsers(
Expand Down
3 changes: 2 additions & 1 deletion src/server/app-router.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { authRouter } from '@ad/src/server/routers/auth';
import { declarationRouter } from '@ad/src/server/routers/declaration';
import { eventRouter } from '@ad/src/server/routers/event';
import { organizationRouter } from '@ad/src/server/routers/organization';
import { systemRouter } from '@ad/src/server/routers/system';
import { ticketingRouter } from '@ad/src/server/routers/ticketing';
import { userRouter } from '@ad/src/server/routers/user';
import { mergeRouters } from '@ad/src/server/trpc';

export const appRouter = mergeRouters(systemRouter, authRouter, userRouter, organizationRouter, ticketingRouter, eventRouter);
export const appRouter = mergeRouters(systemRouter, authRouter, userRouter, organizationRouter, ticketingRouter, eventRouter, declarationRouter);
export type AppRouter = typeof appRouter;
Loading

0 comments on commit a1d6c32

Please sign in to comment.