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:
Gjermund Høsøien Wiggen
2026-06-07 23:28:46 +02:00
parent 04b4e28d21
commit 08b52426b0
4 changed files with 90 additions and 5 deletions

View File

@@ -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;
}