Implement ticket reply functionality
Backend:
- POST /:id/comment endpoint accepting {body, internal?, creator_id?}
- internal=false → Correspond (public reply), internal=true → Comment
- Runs scrip engine on the new transaction so notifications fire
- CommentSchema zod validation
Frontend:
- sendComment() API function in lib/api.ts
- Send button wired with onClick, sending spinner, disabled state
- Error display below reply box, clears on new typing
- Refreshes transaction list after successful send
- Reply/Internal note mode passed as internal flag
This commit is contained in:
@@ -14,3 +14,9 @@ export const UpdateTicketSchema = z.object({
|
||||
status: z.string().min(1).optional(),
|
||||
owner_id: z.string().uuid().optional(),
|
||||
});
|
||||
|
||||
export const CommentSchema = z.object({
|
||||
body: z.string().min(1),
|
||||
creator_id: z.string().optional().default('00000000-0000-0000-0000-000000000000'),
|
||||
internal: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { HTTPException } from 'hono/http-exception';
|
||||
import type { Db } from '../db/index.ts';
|
||||
import { tickets, transactions, customFieldValues, customFields, queues, lifecycles } from '../db/schema.ts';
|
||||
import { eq, asc } from 'drizzle-orm';
|
||||
import { CreateTicketSchema, UpdateTicketSchema } from '../models/ticket.ts';
|
||||
import { CreateTicketSchema, UpdateTicketSchema, CommentSchema } from '../models/ticket.ts';
|
||||
import { ScripEngine } from '../scrip/engine.ts';
|
||||
import { LifecycleValidator } from '../lifecycle/validator.ts';
|
||||
import type { LifecycleDefinition } from '../lifecycle/validator.ts';
|
||||
@@ -231,5 +231,40 @@ export function createTicketsRouter(db: Db): Hono {
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
// POST /:id/comment — add a comment (reply or internal note)
|
||||
router.post('/:id/comment', async (c) => {
|
||||
const id = Number(c.req.param('id'));
|
||||
const body = await c.req.json();
|
||||
const parsed = CommentSchema.parse(body);
|
||||
|
||||
const ticket = await db.query.tickets.findFirst({
|
||||
where: eq(tickets.id, id),
|
||||
});
|
||||
|
||||
if (!ticket) {
|
||||
throw new HTTPException(404, { message: 'Ticket not found' });
|
||||
}
|
||||
|
||||
const transactionType = parsed.internal ? 'Comment' : 'Correspond';
|
||||
|
||||
const [tx] = await db.insert(transactions).values({
|
||||
ticket_id: id,
|
||||
transaction_type: transactionType,
|
||||
data: { body: parsed.body },
|
||||
creator_id: parsed.creator_id,
|
||||
}).returning();
|
||||
|
||||
if (!tx) {
|
||||
throw new HTTPException(500, { message: 'Failed to create comment' });
|
||||
}
|
||||
|
||||
// Run scrips
|
||||
const txList = [tx];
|
||||
const prepared = await scripEngine.prepare(id, txList as any);
|
||||
await scripEngine.commit(prepared);
|
||||
|
||||
return c.json(tx, 201);
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user