Skip to content

Commit

Permalink
Migrate training configuration to be a RemoteDataTable
Browse files Browse the repository at this point in the history
  • Loading branch information
beverloo committed Jan 23, 2024
1 parent bd6ab62 commit b19edd7
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 216 deletions.
107 changes: 17 additions & 90 deletions app/admin/events/[slug]/training/TrainingConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,17 @@
import { useCallback } from 'react';
import { useRouter } from 'next/navigation';

import type { GridRenderCellParams, GridValidRowModel } from '@mui/x-data-grid';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';

import type { PageInfo } from '@app/admin/events/verifyAccessAndFetchPageInfo';
import type { TrainingDefinition } from '@app/api/admin/training';
import type { TrainingsRowModel } from '@app/api/admin/trainings/[[...id]]/route';
import type { UpdatePublicationDefinition } from '@app/api/admin/updatePublication';
import { type DataTableColumn, OLD_DataTable } from '@app/admin/DataTable';
import { PublishAlert } from '@app/admin/components/PublishAlert';
import { RemoteDataTable, type RemoteDataTableColumn } from '@app/admin/components/RemoteDataTable';
import { dayjs } from '@lib/DateTime';
import { issueServerAction } from '@lib/issueServerAction';

/**
* Configuration options available for a training session. Can be amended by this page.
*/
export interface TrainingConfigurationEntry {
/**
* Unique ID of this entry in the training configuration.
*/
id: number;

/**
* Address at which the training will be taking place.
*/
trainingAddress?: string;

/**
* Maximum capacity of the training.
*/
trainingCapacity?: number;

/**
* Date and time at which the training will commence.
*/
trainingStart?: string;

/**
* Date and time at which the training will conclude.
*/
trainingEnd?: string;
}

/**
* Props accepted by the <TrainingConfiguration> component.
*/
Expand All @@ -56,11 +25,6 @@ export interface TrainingConfigurationProps {
* Information about the event for which training sessions are being shown.
*/
event: PageInfo['event'];

/**
* The training sessions that can be displayed by this component.
*/
trainings: TrainingConfigurationEntry[];
}

/**
Expand All @@ -70,48 +34,6 @@ export interface TrainingConfigurationProps {
export function TrainingConfiguration(props: TrainingConfigurationProps) {
const { event } = props;

async function commitAdd(): Promise<TrainingConfigurationEntry> {
const response = await issueServerAction<TrainingDefinition>('/api/admin/training', {
event: event.slug,
create: { /* empty payload */ }
});

if (!response.id)
throw new Error('The server was unable to create a new training session.');

return {
id: response.id,
trainingCapacity: 10,
trainingAddress: undefined,
trainingStart: event.startTime,
trainingEnd: event.endTime,
};
}

async function commitDelete(oldRow: GridValidRowModel) {
await issueServerAction<TrainingDefinition>('/api/admin/training', {
event: event.slug,
delete: {
id: oldRow.id,
},
});
}

async function commitEdit(newRow: GridValidRowModel, oldRow: GridValidRowModel) {
const response = await issueServerAction<TrainingDefinition>('/api/admin/training', {
event: event.slug,
update: {
id: oldRow.id,
trainingAddress: newRow.trainingAddress,
trainingStart: newRow.trainingStart,
trainingEnd: newRow.trainingEnd,
trainingCapacity: newRow.trainingCapacity,
}
});

return response.success ? newRow : oldRow;
}

const router = useRouter();

const onPublish = useCallback(async (domEvent: unknown, publish: boolean) => {
Expand All @@ -126,42 +48,46 @@ export function TrainingConfiguration(props: TrainingConfigurationProps) {

}, [ event, router ]);

const columns: DataTableColumn[] = [
const context = {
event: event.slug,
};

const columns: RemoteDataTableColumn<TrainingsRowModel>[] = [
{
field: 'id',
headerName: /* empty= */ '',
sortable: false,
width: 50,
},
{
field: 'trainingStart',
field: 'start',
headerName: 'Date (start time)',
editable: true,
sortable: true,
flex: 2,

renderCell: (params: GridRenderCellParams) =>
renderCell: params =>
dayjs(params.value).tz(event.timezone).format('YYYY-MM-DD [at] H:mm'),
},
{
field: 'trainingEnd',
field: 'end',
headerName: 'Date (end time)',
editable: true,
sortable: true,
flex: 2,

renderCell: (params: GridRenderCellParams) =>
renderCell: params =>
dayjs(params.value).tz(event.timezone).format('YYYY-MM-DD [at] H:mm'),
},
{
field: 'trainingAddress',
field: 'address',
headerName: 'Address',
editable: true,
sortable: true,
flex: 3,
},
{
field: 'trainingCapacity',
field: 'capacity',
headerName: 'Capacity',
editable: true,
sortable: true,
Expand All @@ -180,9 +106,10 @@ export function TrainingConfiguration(props: TrainingConfigurationProps) {
? 'Training information has been published to volunteers.'
: 'Training information has not yet been published to volunteers.' }
</PublishAlert>
<OLD_DataTable commitAdd={commitAdd} commitDelete={commitDelete} commitEdit={commitEdit}
messageSubject="training" rows={props.trainings} columns={columns}
disableFooter dense />
<RemoteDataTable endpoint="/api/admin/trainings" context={context}
columns={columns} defaultSort={{ field: 'start', sort: 'asc' }}
disableFooter enableCreate enableDelete enableUpdate
refreshOnUpdate subject="training" />
</Paper>
);
}
2 changes: 1 addition & 1 deletion app/admin/events/[slug]/training/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export default async function EventTrainingPage(props: NextRouterParams<'slug'>)
</Collapse>
<TrainingExternal event={event} participants={extraParticipants}
trainings={trainingOptions} />
<TrainingConfiguration event={event} trainings={trainings} />
<TrainingConfiguration event={event} />
</>
);
}
Expand Down
126 changes: 1 addition & 125 deletions app/api/admin/training.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { z } from 'zod';
import type { ActionProps } from '../Action';
import { Log, LogSeverity, LogType } from '@lib/Log';
import { Privilege } from '@lib/auth/Privileges';
import { dayjs } from '@lib/DateTime';
import { executeAccessCheck } from '@lib/auth/AuthenticationContext';
import { getEventBySlug } from '@lib/EventLoader';
import db, { tTrainingsAssignments, tTrainings } from '@lib/database';
import db, { tTrainingsAssignments } from '@lib/database';

/**
* Interface definition for the Training API, exposed through /api/admin/training.
Expand Down Expand Up @@ -41,52 +40,6 @@ export const kTrainingDefinition = z.object({
confirmed: z.boolean(),

}).optional(),

/**
* Must be set to an empty object when a new training session is being added.
*/
create: z.object({ /* empty object */ }).optional(),

/**
* Must be set to an object when a training session is being deleted.
*/
delete: z.object({
/**
* Unique ID of the training session that should be removed.
*/
id: z.number(),
}).optional(),

/**
* Must be set to an object when a training session is being updated.
*/
update: z.object({
/**
* Unique ID of the training session that is being updated.
*/
id: z.number(),

/**
* Address at which the training will be taking place.
*/
trainingAddress: z.string().optional(),

/**
* Date at which the training will be taking place.
*/
trainingStart: z.string().optional(),

/**
* Date at which the training will be taking place.
*/
trainingEnd: z.string().optional(),

/**
* Maximum number of people that can join this training session.
*/
trainingCapacity: z.number().optional(),

}).optional(),
}),
response: z.strictObject({
/**
Expand Down Expand Up @@ -186,82 +139,5 @@ export async function training(request: Request, props: ActionProps): Promise<Re
return { success: true };
}

// Operation: create
if (request.create !== undefined) {
const insertId =
await db.insertInto(tTrainings)
.values({ eventId: event.eventId })
.returningLastInsertedId()
.executeInsert();

await Log({
type: LogType.AdminEventTrainingMutation,
severity: LogSeverity.Info,
sourceUser: props.user,
data: {
eventName: event.shortName,
mutation: 'Created',
},
});

return { success: true, id: insertId };
}

// Operation: delete
if (request.delete !== undefined) {
const affectedRows =
await db.deleteFrom(tTrainings)
.where(tTrainings.trainingId.equals(request.delete.id))
.and(tTrainings.eventId.equals(event.eventId))
.executeDelete(/* min= */ 0, /* max= */ 1);

if (affectedRows > 0) {
await Log({
type: LogType.AdminEventTrainingMutation,
severity: LogSeverity.Info,
sourceUser: props.user,
data: {
eventName: event.shortName,
mutation: 'Deleted',
},
});
}

return { success: !!affectedRows };
}

// Operation: update
if (request.update !== undefined) {
const affectedRows = await db.update(tTrainings)
.set({
trainingAddress: request.update.trainingAddress,
trainingStart:
request.update.trainingStart ? dayjs.utc(request.update.trainingStart)
: undefined,
trainingEnd:
request.update.trainingEnd ? dayjs.utc(request.update.trainingEnd)
: undefined,

trainingCapacity: request.update.trainingCapacity,
})
.where(tTrainings.trainingId.equals(request.update.id))
.and(tTrainings.eventId.equals(event.eventId))
.executeUpdate(/* min= */ 0, /* max= */ 1);

if (affectedRows > 0) {
await Log({
type: LogType.AdminEventTrainingMutation,
severity: LogSeverity.Info,
sourceUser: props.user,
data: {
eventName: event.shortName,
mutation: 'Updated',
},
});
}

return { success: !!affectedRows };
}

return { success: false };
}
Loading

0 comments on commit b19edd7

Please sign in to comment.