-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable notes to be managed in the schedule app
- Loading branch information
Showing
8 changed files
with
261 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2024 Peter Beverloo & AnimeCon. All rights reserved. | ||
// Use of this source code is governed by a MIT license that can be found in the LICENSE file. | ||
|
||
import { NextRequest } from 'next/server'; | ||
import { executeAction } from '../../../Action'; | ||
|
||
import { updateNotes, kUpdateNotesDefinition } from '../updateNotes'; | ||
|
||
/** | ||
* The /api/event/schedule/notes endpoint can be used to update the notes of a particular volunteer. | ||
*/ | ||
export async function PUT(request: NextRequest): Promise<Response> { | ||
return executeAction(request, kUpdateNotesDefinition, updateNotes); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2024 Peter Beverloo & AnimeCon. All rights reserved. | ||
// Use of this source code is governed by a MIT license that can be found in the LICENSE file. | ||
|
||
import { notFound } from 'next/navigation'; | ||
import { z } from 'zod'; | ||
|
||
import type { ActionProps } from '../../Action'; | ||
import type { ApiDefinition, ApiRequest, ApiResponse } from '../../Types'; | ||
import { LogSeverity, RegistrationStatus } from '@lib/database/Types'; | ||
import { Log, LogType } from '@lib/Log'; | ||
import { getEventBySlug } from '@lib/EventLoader'; | ||
import db, { tUsersEvents } from '@lib/database'; | ||
|
||
/** | ||
* Interface definition for the Schedule API, exposed through /api/event/schedule/notes | ||
*/ | ||
export const kUpdateNotesDefinition = z.object({ | ||
request: z.object({ | ||
/** | ||
* Unique slug of the event with which the notes should be associated. | ||
*/ | ||
event: z.string(), | ||
|
||
/** | ||
* Unique ID of the user whose notes should be updated. | ||
*/ | ||
userId: z.number(), | ||
|
||
/** | ||
* The notes as they should be stored in the database. May be empty. | ||
*/ | ||
notes: z.string(), | ||
}), | ||
response: z.strictObject({ | ||
/** | ||
* Whether the notes was updated successfully. | ||
*/ | ||
success: z.boolean(), | ||
|
||
/** | ||
* Error message when something went wrong. Will be presented to the user. | ||
*/ | ||
error: z.string().optional(), | ||
}), | ||
}); | ||
|
||
export type UpdateNotesDefinition = ApiDefinition<typeof kUpdateNotesDefinition>; | ||
|
||
type Request = ApiRequest<typeof kUpdateNotesDefinition>; | ||
type Response = ApiResponse<typeof kUpdateNotesDefinition>; | ||
|
||
/** | ||
* API through which the notes associated with a volunteer can be updated. | ||
*/ | ||
export async function updateNotes(request: Request, props: ActionProps): Promise<Response> { | ||
if (!props.user || !props.authenticationContext.user) | ||
notFound(); | ||
|
||
const event = await getEventBySlug(request.event); | ||
if (!event) | ||
notFound(); | ||
|
||
const affectedRows = await db.update(tUsersEvents) | ||
.set({ | ||
registrationNotes: !!request.notes.length ? request.notes : undefined | ||
}) | ||
.where(tUsersEvents.eventId.equals(event.id)) | ||
.and(tUsersEvents.userId.equals(request.userId)) | ||
.and(tUsersEvents.registrationStatus.equals(RegistrationStatus.Accepted)) | ||
.executeUpdate(); | ||
|
||
if (!!affectedRows) { | ||
await Log({ | ||
type: LogType.EventVolunteerNotes, | ||
severity: LogSeverity.Info, | ||
sourceUser: props.user, | ||
targetUser: request.userId, | ||
data: { | ||
event: event.shortName, | ||
notes: request.notes, | ||
}, | ||
}); | ||
} | ||
|
||
return { success: !!affectedRows }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright 2024 Peter Beverloo & AnimeCon. All rights reserved. | ||
// Use of this source code is governed by a MIT license that can be found in the LICENSE file. | ||
|
||
'use client'; | ||
|
||
import { useCallback, useEffect, useState, type ChangeEvent } from 'react'; | ||
|
||
import Alert from '@mui/material/Alert'; | ||
import Button from '@mui/material/Button'; | ||
import Collapse from '@mui/material/Collapse'; | ||
import Dialog from '@mui/material/Dialog'; | ||
import DialogActions from '@mui/material/DialogActions'; | ||
import DialogContent from '@mui/material/DialogContent'; | ||
import DialogTitle from '@mui/material/DialogTitle'; | ||
import LoadingButton from '@mui/lab/LoadingButton'; | ||
import TextField from '@mui/material/TextField'; | ||
|
||
/** | ||
* Props accepted by the <NotesEditorDialog> component. | ||
*/ | ||
export interface NotesEditorDialogProps { | ||
/** | ||
* To be called when the notes dialog has been closed. | ||
*/ | ||
onClose?: () => void; | ||
|
||
/** | ||
* To be called when the updates notes are to be submitted. When the return value is truthy, | ||
* the dialog will be closed, whereas an error will be shown in case of a failure. | ||
*/ | ||
onSubmit?: (notes: string) => Promise<boolean>; | ||
|
||
/** | ||
* The notes that are stored for the context at the moment. | ||
*/ | ||
notes?: string; | ||
|
||
/** | ||
* Whether the dialog should be presented to the user. | ||
*/ | ||
open?: boolean; | ||
} | ||
|
||
/** | ||
* The <NotesEditorDialog> component displays a dialog, when opened, in which the notes for a given | ||
* event or volunteer can be changed. Markdown is supported in updated notes. | ||
*/ | ||
export default function NotesEditorDialog(props: NotesEditorDialogProps) { | ||
const { onClose, onSubmit, open } = props; | ||
|
||
const [ error, setError ] = useState<boolean>(false); | ||
const [ loading, setLoading ] = useState<boolean>(false); | ||
|
||
const [ notes, setNotes ] = useState<string>(props.notes || ''); | ||
|
||
useEffect(() => setNotes(props.notes || ''), [ props.notes ]); | ||
|
||
const handleClose = useCallback(() => { | ||
setTimeout(() => { | ||
setError(false); | ||
setNotes(props.notes || ''); | ||
}, 350); | ||
|
||
if (!!onClose) | ||
onClose(); | ||
|
||
}, [ onClose, props.notes, setNotes ]); | ||
|
||
const handleUpdateNotes = useCallback((event: ChangeEvent<HTMLTextAreaElement>) => { | ||
setNotes(event.target.value); | ||
|
||
}, [ /* no deps */ ]); | ||
|
||
const handleSubmit = useCallback(async () => { | ||
setError(false); | ||
setLoading(true); | ||
try { | ||
if (!!await onSubmit?.(notes)) | ||
handleClose(); | ||
else | ||
setError(true); | ||
} catch (error: any) { | ||
setError(true); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}, [ handleClose, notes, onSubmit ]); | ||
|
||
return ( | ||
<Dialog onClose={handleClose} open={!!open} fullWidth> | ||
<DialogTitle sx={{ mb: -1 }}> | ||
What should we keep in mind? | ||
</DialogTitle> | ||
<DialogContent sx={{ pt: '8px !important' }}> | ||
<TextField fullWidth multiline label="Notes" size="small" | ||
value={notes} onChange={handleUpdateNotes} /> | ||
<Collapse in={!!error}> | ||
<Alert severity="error" sx={{ mt: 2 }}> | ||
The notes could not be saved. Try again later? | ||
</Alert> | ||
</Collapse> | ||
</DialogContent> | ||
<DialogActions sx={{ pr: 3, pb: 2, mt: -1 }}> | ||
<Button color="inherit" onClick={handleClose}>Cancel</Button> | ||
<LoadingButton variant="contained" onClick={handleSubmit} loading={!!loading}> | ||
Save | ||
</LoadingButton> | ||
</DialogActions> | ||
</Dialog> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters