Allow editing existing date/time options in Meeting Planner polls

Add PUT endpoint for updating individual poll options and replace
read-only text display with inline DatePicker/TimePicker controls
in the edit drawer.

Bunker Admin
This commit is contained in:
bunker-admin 2026-03-02 10:25:05 -07:00
parent b30e4301bb
commit e3045966a0
4 changed files with 80 additions and 3 deletions

View File

@ -281,6 +281,19 @@ export default function MeetingPlannerPage() {
}
};
const handleUpdateOption = async (optionId: string, field: string, value: string) => {
if (!editPoll) return;
try {
await api.put(`/meeting-planner/${editPoll.id}/options/${optionId}`, { [field]: value });
// Refresh edit poll data
const { data } = await api.get<PollDetailResponse>(`/meeting-planner/${editPoll.id}`);
setEditPoll(data);
message.success('Option updated');
} catch {
message.error('Failed to update option');
}
};
const handleAddOption = async (option: { date: string; startTime: string; endTime: string }) => {
if (!editPoll) return;
try {
@ -836,9 +849,31 @@ export default function MeetingPlannerPage() {
{editPoll.options?.map((opt) => (
<Row key={opt.id} gutter={8} align="middle" style={{ marginBottom: 8 }}>
<Col flex="auto">
<Text>
{dayjs(opt.date).format('MMM D, YYYY')} {opt.startTime}{opt.endTime}
</Text>
<Space wrap>
<DatePicker
format="YYYY-MM-DD"
value={dayjs(opt.date)}
onChange={(d) => {
if (d) handleUpdateOption(opt.id, 'date', d.format('YYYY-MM-DD'));
}}
/>
<TimePicker
format="HH:mm"
minuteStep={15}
value={dayjs(opt.startTime, 'HH:mm')}
onChange={(t) => {
if (t) handleUpdateOption(opt.id, 'startTime', t.format('HH:mm'));
}}
/>
<TimePicker
format="HH:mm"
minuteStep={15}
value={dayjs(opt.endTime, 'HH:mm')}
onChange={(t) => {
if (t) handleUpdateOption(opt.id, 'endTime', t.format('HH:mm'));
}}
/>
</Space>
</Col>
<Col>
{(editPoll.options?.length ?? 0) > 1 && (

View File

@ -5,6 +5,7 @@ import {
createPollSchema,
updatePollSchema,
addOptionsSchema,
updateOptionSchema,
submitVotesSchema,
submitCommentSchema,
finalizePollSchema,
@ -76,6 +77,16 @@ adminRouter.post('/:id/options', validate(addOptionsSchema), async (req: Request
} catch (err) { next(err); }
});
// Update option
adminRouter.put('/:id/options/:optionId', validate(updateOptionSchema), async (req: Request, res: Response, next: NextFunction) => {
try {
const id = req.params.id as string;
const optionId = req.params.optionId as string;
const poll = await meetingPlannerService.updateOption(id, optionId, req.body);
res.json(poll);
} catch (err) { next(err); }
});
// Remove option
adminRouter.delete('/:id/options/:optionId', async (req: Request, res: Response, next: NextFunction) => {
try {

View File

@ -60,6 +60,12 @@ export const convertToShiftSchema = z.object({
cutId: z.string().optional(),
});
export const updateOptionSchema = z.object({
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD').optional(),
startTime: z.string().regex(/^\d{2}:\d{2}$/, 'Start time must be HH:MM').optional(),
endTime: z.string().regex(/^\d{2}:\d{2}$/, 'End time must be HH:MM').optional(),
});
export const listPollsSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
@ -74,4 +80,5 @@ export type SubmitVotesInput = z.infer<typeof submitVotesSchema>;
export type SubmitCommentInput = z.infer<typeof submitCommentSchema>;
export type FinalizePollInput = z.infer<typeof finalizePollSchema>;
export type ConvertToShiftInput = z.infer<typeof convertToShiftSchema>;
export type UpdateOptionInput = z.infer<typeof updateOptionSchema>;
export type ListPollsInput = z.infer<typeof listPollsSchema>;

View File

@ -8,6 +8,7 @@ import type {
CreatePollInput,
UpdatePollInput,
AddOptionsInput,
UpdateOptionInput,
SubmitVotesInput,
SubmitCommentInput,
FinalizePollInput,
@ -219,6 +220,29 @@ export const meetingPlannerService = {
return this.findById(pollId);
},
async updateOption(pollId: string, optionId: string, data: UpdateOptionInput) {
const poll = await prisma.schedulingPoll.findUnique({ where: { id: pollId } });
if (!poll) throw new AppError(404, 'Poll not found');
if (poll.status !== 'OPEN') throw new AppError(400, 'Cannot update options on a non-open poll');
const option = await prisma.schedulingPollOption.findFirst({
where: { id: optionId, pollId },
});
if (!option) throw new AppError(404, 'Option not found');
const updateData: Prisma.SchedulingPollOptionUncheckedUpdateInput = {};
if (data.date !== undefined) updateData.date = new Date(data.date);
if (data.startTime !== undefined) updateData.startTime = data.startTime;
if (data.endTime !== undefined) updateData.endTime = data.endTime;
await prisma.schedulingPollOption.update({
where: { id: optionId },
data: updateData,
});
return this.findById(pollId);
},
async removeOption(pollId: string, optionId: string) {
const option = await prisma.schedulingPollOption.findFirst({
where: { id: optionId, pollId },