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:
parent
b30e4301bb
commit
e3045966a0
@ -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 && (
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user