Skip to content

Commit

Permalink
Control course visibility
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianCassayre committed Jan 4, 2024
1 parent 887fb7d commit d3d647c
Show file tree
Hide file tree
Showing 11 changed files with 52 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `course` ADD COLUMN `visible` BOOLEAN NOT NULL DEFAULT true;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ model Course {
price Int @db.UnsignedInt
dateStart DateTime @db.Timestamp(0) @map("date_start")
dateEnd DateTime @db.Timestamp(0) @map("date_end")
visible Boolean @default(true)
isCanceled Boolean @default(false) @map("is_canceled")
cancelationReason String? @db.Text @map("cancelation_reason")
notes String? @db.Text
Expand Down
1 change: 1 addition & 0 deletions src/common/schemas/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const courseFindSchema = z.object({
const courseSchemaBase = z.object({
price: z.number().int().min(0).max(99),
slots: z.number().int().min(1).max(99),
visible: z.boolean(),
});

export const courseUpdateSchema = courseSchemaBase.merge(courseFindSchema);
Expand Down
13 changes: 10 additions & 3 deletions src/components/form/forms/course.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
DatePickerElement,
FormContainer,
FormContainer, SwitchElement,
TextFieldElement,
useFormContext
useFormContext,
} from 'react-hook-form-mui';
import { z } from 'zod';
import {
Expand Down Expand Up @@ -181,8 +181,9 @@ const DatesSelectionList = () => {
);
};

const courseFormDefaultValues: { dates: Date[], modelId?: number } = {
const courseFormDefaultValues: { dates: Date[], modelId?: number, visible: boolean } = {
dates: [] satisfies Date[],
visible: true,
};

const useProceduresToInvalidate = () => {
Expand Down Expand Up @@ -262,6 +263,9 @@ const CourseCreateFormContent = () => {
<Grid item xs={12}>
<InputPrice name="price" disabled={isUsingModel} />
</Grid>
<Grid item xs={12}>
<SwitchElement name="visible" label="Visible"/>
</Grid>
<Grid item xs={12} container justifyContent="center">
<Grid item xs={12} md={6} lg={4}>
<DatesSelectionList />
Expand Down Expand Up @@ -316,6 +320,9 @@ export const CourseUpdateForm = ({ queryData }: { queryData: ParsedUrlQuery }) =
<Grid item xs={12}>
<InputPrice name="price" />
</Grid>
<Grid item xs={12}>
<SwitchElement name="visible" label="Visible" />
</Grid>
</Grid>
</UpdateFormContent>
);
Expand Down
15 changes: 13 additions & 2 deletions src/components/grid/grids/CourseGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Cancel, Edit, Notes, Visibility } from '@mui/icons-material';
import { Cancel, Edit, Notes, Public, Visibility, VisibilityOff } from '@mui/icons-material';
import { formatDateDDsmmYYYY, formatTimeHHhMM, formatWeekday } from '../../../common/date';
import { Course, CourseType } from '@prisma/client';
import { CourseTypeNames, getCourseStatusWithRegistrations } from '../../../common/course';
Expand All @@ -8,7 +8,7 @@ import { AsyncGrid } from '../AsyncGrid';
import { useRouter } from 'next/router';
import { CourseStatusChip } from '../../CourseStatusChip';
import { GridRenderCellParams, GridValueFormatterParams } from '@mui/x-data-grid/models/params/gridCellParams';
import { Box } from '@mui/material';
import { Box, Tooltip } from '@mui/material';
import { CancelCourseDialog } from '../../dialogs/CancelCourseDialog';
import { trpc } from '../../../common/trpc';
import { useSnackbar } from 'notistack';
Expand Down Expand Up @@ -140,6 +140,17 @@ export const CourseGrid: React.FunctionComponent<CourseGridProps> = ({ future, c
)
},
},
{
field: 'visible',
headerName: 'Visibilité',
minWidth: 80,
flex: 0.5,
renderCell: ({ row: { visible } }: GridRenderCellParams<CourseItem>) => (
<Tooltip title={visible ? 'Publique' : 'Cachée'}>
{visible ? <Public color="action"/> : <VisibilityOff color="error"/>}
</Tooltip>
),
},
{
field: 'notes',
headerName: 'Notes',
Expand Down
10 changes: 9 additions & 1 deletion src/pages/administration/seances/planning/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Edit,
EmojiPeople,
Event, KeyboardDoubleArrowLeft, KeyboardDoubleArrowRight,
Notes
Notes, Public, VisibilityOff,
} from '@mui/icons-material';
import { Course, Prisma } from '@prisma/client';
import { useRouter } from 'next/router';
Expand Down Expand Up @@ -171,6 +171,14 @@ const CourseContent: React.FunctionComponent<CourseContentProps> = ({ course }:
{ header: 'Prix', value: `${course.price} €` },
{ header: 'Date', value: formatDateDDsMMsYYYY(course.dateStart) },
{ header: 'Heures', value: `${formatTimeHHhMM(course.dateStart)} à ${formatTimeHHhMM(course.dateEnd)}` },
{ header: 'Visibilité', value:
<Chip
label={course.visible ? 'Publique' : 'Cachée'}
color={course.visible ? undefined : 'error'}
icon={course.visible ? <Public /> : <VisibilityOff />}
variant="outlined" size="small"
/>
},
]}
/>
</Grid>
Expand Down
9 changes: 5 additions & 4 deletions src/server/controllers/routers/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@ export const courseRouter = router({
})*/
createMany: backofficeWriteProcedure
.input(courseCreateManySchema)
.mutation(async ({ input: { type, timeStart, timeEnd, price, slots, dates } }) =>
createCourses({ data: { type, price, slots, timeStart, timeEnd, dates } })),
.mutation(async ({ input: { type, timeStart, timeEnd, price, slots, visible, dates } }) =>
createCourses({ data: { type, price, slots, timeStart, timeEnd, visible, dates } })),
update: backofficeWriteProcedure
.input(z.strictObject({
id: z.number().int().min(0),
slots: z.number().int().min(0),
price: z.number().int().min(0),
visible: z.boolean(),
}))
.mutation(async ({ input: { id, slots, price } }) =>
await updateCourse({ where: { id }, data: { slots, price } })),
.mutation(async ({ input: { id, slots, price, visible } }) =>
await updateCourse({ where: { id }, data: { slots, price, visible } })),
updateNotes: backofficeWriteProcedure
.input(courseUpdateNotesSchema)
.mutation(async ({ input: { id, notes } }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/server/controllers/routers/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const publicRouter = router({
findAllFutureCourses: procedure
.query(async () => {
const date = new Date();
const courses = await prisma.course.findMany({ where: { dateStart: { gt: date }, isCanceled: false }, include: { registrations: true }, orderBy: { dateStart: 'asc' } });
const courses = await prisma.course.findMany({ where: { dateStart: { gt: date }, isCanceled: false, visible: true }, include: { registrations: true }, orderBy: { dateStart: 'asc' } });
return courses.map(({ id, type, slots, price, dateStart, dateEnd, registrations }) => ({
id, type, slots, price, dateStart, dateEnd,
registrations: registrations.filter(({ isUserCanceled }) => !isUserCanceled).length,
Expand Down
9 changes: 5 additions & 4 deletions src/server/services/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { courseCreateManySchema } from '../../common/schemas/course';
export const findCourse = async (args: { where: Prisma.CourseWhereUniqueInput }) =>
prisma.course.findUniqueOrThrow({ where: args.where, include: { registrations: true } });

export const findUpdateCourse = (args: { where: Prisma.CourseWhereUniqueInput }) => prisma.course.findUniqueOrThrow({ where: args.where, select: { id: true, slots: true, price: true } });
export const findUpdateCourse = (args: { where: Prisma.CourseWhereUniqueInput }) => prisma.course.findUniqueOrThrow({ where: args.where, select: { id: true, slots: true, price: true, visible: true } });

export const findUpdateCourseNotes = (args: { where: Prisma.CourseWhereUniqueInput }) => prisma.course.findUniqueOrThrow({ where: args.where, select: { id: true, notes: true } });

Expand Down Expand Up @@ -38,7 +38,7 @@ export const findCoursesRelated = async (args: { where: Prisma.CourseWhereUnique
};
});

export const updateCourse = async (args: { where: Prisma.CourseWhereUniqueInput, data: Partial<Pick<Course, 'slots' | 'price' | 'notes'>> }) => {
export const updateCourse = async (args: { where: Prisma.CourseWhereUniqueInput, data: Partial<Pick<Course, 'slots' | 'price' | 'visible' | 'notes'>> }) => {
const { where: { id }, data: { slots, price } } = args;
return await writeTransaction(async (prisma) => {
const course = await prisma.course.findUniqueOrThrow({ where: args.where, select: { isCanceled: true, dateEnd: true, price: true, registrations: { select: { isUserCanceled: true, orderPurchased: { select: { id: true } } } } } });
Expand Down Expand Up @@ -95,9 +95,9 @@ export const cancelCourse = async (args: { where: Prisma.CourseWhereUniqueInput,
return result;
};

export const createCourses = async (args: { data: Pick<Course, 'type' | 'price' | 'slots'> & { timeStart: string, timeEnd: string, dates: Date[] } }) => {
export const createCourses = async (args: { data: Pick<Course, 'type' | 'price' | 'slots' | 'visible'> & { timeStart: string, timeEnd: string, dates: Date[] } }) => {
courseCreateManySchema.parse({ ...args.data });
const { data: { type, price, slots, timeStart, timeEnd, dates } } = args;
const { data: { type, price, slots, visible, timeStart, timeEnd, dates } } = args;
dates.sort((a, b) => a.getTime() - b.getTime());
const withTime = (date: Date, time: string): Date => {
const copy = new Date(date);
Expand All @@ -114,6 +114,7 @@ export const createCourses = async (args: { data: Pick<Course, 'type' | 'price'
type,
price,
slots,
visible,
dateStart: withTime(date, timeStart),
dateEnd: withTime(date, timeEnd)
};
Expand Down
3 changes: 3 additions & 0 deletions src/server/services/courseRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export const createCourseRegistrations = async (prisma: Prisma.TransactionClient
const newRegistrationsForUser: (typeof newRegistrationsPerUser)[0][1] = [];
for (const courseId of args.data.courses) {
const course = await prisma.course.findUniqueOrThrow({ where: { id: courseId }, include: { registrations: { where: { isUserCanceled: false } } } });
if (!admin && !course.visible) {
throw new ServiceError(ServiceErrorCode.CourseRestricted);
}
if (course.isCanceled) {
throw new ServiceError(ServiceErrorCode.CourseCanceledNoRegistration);
}
Expand Down
2 changes: 2 additions & 0 deletions src/server/services/helpers/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum ServiceErrorCode {
UsersCannotBeMerged,
OrderCannotInferRecipient,
OrderInvoiceInapplicable,
CourseRestricted,
}

export class ServiceError<T extends ServiceErrorCode> extends Error {
Expand Down Expand Up @@ -81,4 +82,5 @@ export const ServiceErrorCodeMessages: { [K in ServiceErrorCode]: string } = {
[ServiceErrorCode.UsersCannotBeMerged]: `Ces utilisateurs ne peuvent pas être fusionnés`,
[ServiceErrorCode.OrderCannotInferRecipient]: `Le bénéficiaire ne peut pas être déduit à partir des articles de ce paiement`,
[ServiceErrorCode.OrderInvoiceInapplicable]: `Une facture ne peut pas être générée pour ce type de paiement`,
[ServiceErrorCode.CourseRestricted]: `L'inscription à cette séance est restreinte`,
};

0 comments on commit d3d647c

Please sign in to comment.