feat: team assignment on tickets + My team's tickets view
Backend: - team_id column on tickets table - team_id filter in GET /tickets (resolves team members) - team_id in UpdateTicketSchema + PATCH handler - SetTeam transaction type Frontend: - Team selector in ticket detail properties sidebar - My team's tickets in sidebar (when user belongs to a team) - team_id passed through to API from ticket list page Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ export const tickets = pgTable('tickets', {
|
||||
queue_id: uuid('queue_id').notNull().references(() => queues.id),
|
||||
status: text('status').notNull(),
|
||||
owner_id: uuid('owner_id').references(() => users.id),
|
||||
team_id: uuid('team_id').references(() => teams.id, { onDelete: 'set null' }),
|
||||
creator_id: uuid('creator_id').notNull().references(() => users.id),
|
||||
created_at: timestamp('created_at', { withTimezone: true }).defaultNow(),
|
||||
updated_at: timestamp('updated_at', { withTimezone: true }).defaultNow(),
|
||||
|
||||
@@ -15,6 +15,7 @@ export const UpdateTicketSchema = z.object({
|
||||
subject: z.string().min(1).optional(),
|
||||
status: z.string().min(1).optional(),
|
||||
owner_id: z.string().uuid().nullable().optional(),
|
||||
team_id: z.string().uuid().nullable().optional(),
|
||||
});
|
||||
|
||||
export const CommentSchema = z.object({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import type { Db } from '../db/index.ts';
|
||||
import { tickets, transactions, customFieldValues, customFields, queues, lifecycles, queueCustomFields } from '../db/schema.ts';
|
||||
import { tickets, transactions, customFieldValues, customFields, queues, lifecycles, queueCustomFields, teamMembers } from '../db/schema.ts';
|
||||
import { and, eq, asc, or, ilike, isNull, inArray, exists, sql } from 'drizzle-orm';
|
||||
import { CreateTicketSchema, UpdateTicketSchema, CommentSchema } from '../models/ticket.ts';
|
||||
import { ScripEngine } from '../scrip/engine.ts';
|
||||
@@ -26,6 +26,7 @@ export function createTicketsRouter(db: Db): Hono {
|
||||
const queueId = c.req.query('queue_id');
|
||||
const status = c.req.query('status');
|
||||
const ownerId = c.req.query('owner_id');
|
||||
const teamId = c.req.query('team_id');
|
||||
const query = c.req.query('q')?.trim() ?? '';
|
||||
const limit = c.req.query('limit') ? Number(c.req.query('limit')) : undefined;
|
||||
const cfFilters = [...params.entries()]
|
||||
@@ -49,6 +50,18 @@ export function createTicketsRouter(db: Db): Hono {
|
||||
ownerId === 'unassigned' ? isNull(tickets.owner_id) : eq(tickets.owner_id, ownerId)
|
||||
);
|
||||
}
|
||||
if (teamId) {
|
||||
// Resolve team members and filter tickets by those owner_ids
|
||||
const members = await db.query.teamMembers.findMany({
|
||||
where: eq(teamMembers.team_id, teamId),
|
||||
});
|
||||
const memberIds = members.map((m) => m.user_id);
|
||||
if (memberIds.length > 0) {
|
||||
conditions.push(inArray(tickets.owner_id, memberIds));
|
||||
} else {
|
||||
conditions.push(isNull(tickets.owner_id)); // empty team = no results
|
||||
}
|
||||
}
|
||||
|
||||
// Text search: push to SQL via ilike on ticket columns + queue name join
|
||||
if (query) {
|
||||
@@ -323,6 +336,17 @@ export function createTicketsRouter(db: Db): Hono {
|
||||
});
|
||||
}
|
||||
|
||||
if (parsed.team_id !== undefined && parsed.team_id !== (ticket as any).team_id) {
|
||||
txList.push({
|
||||
ticket_id: id,
|
||||
transaction_type: 'SetTeam' as const,
|
||||
field: 'team_id',
|
||||
old_value: (ticket as any).team_id ?? null,
|
||||
new_value: parsed.team_id,
|
||||
creator_id: '00000000-0000-0000-0000-000000000000',
|
||||
});
|
||||
}
|
||||
|
||||
// Update the ticket
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (parsed.subject) updateData.subject = parsed.subject;
|
||||
@@ -348,6 +372,7 @@ export function createTicketsRouter(db: Db): Hono {
|
||||
}
|
||||
}
|
||||
if (parsed.owner_id !== undefined) updateData.owner_id = parsed.owner_id;
|
||||
if (parsed.team_id !== undefined) updateData.team_id = parsed.team_id;
|
||||
updateData.updated_at = new Date();
|
||||
|
||||
const [updated] = await db.update(tickets)
|
||||
|
||||
Reference in New Issue
Block a user