feat: enhance frontend UI — command palette, admin redesign, API coverage
Types + API: - Add User, TemplatePreview, QueueCustomField types - Add getUsers, getTemplates, createTemplate, updateTemplate, previewTemplate, updateQueue, updateLifecycle, updateCustomField API functions UI: - Command palette: keyboard-first navigation with fuzzy ticket search - Admin: comprehensive redesign with tab-based layout (Queues, Lifecycles, Scrips, Custom Fields, Templates, Users) - Ticket list: improved inbox-style rows with quick actions - Ticket detail: enhanced conversation thread and properties sidebar - App shell: sidebar visual refinement with active indicator bar - Theme toggle: smoother transitions Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import type {
|
||||
Ticket,
|
||||
Queue,
|
||||
User,
|
||||
Transaction,
|
||||
Scrip,
|
||||
Template,
|
||||
TemplatePreview,
|
||||
Lifecycle,
|
||||
LifecycleDefinition,
|
||||
CustomField,
|
||||
QueueCustomField,
|
||||
PreviewResult,
|
||||
UpdateResult,
|
||||
} from "./types";
|
||||
@@ -28,10 +33,23 @@ async function request<T>(url: string, options?: RequestInit): Promise<{ data: T
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTickets(params?: { queue_id?: string; status?: string }): Promise<{ data: Ticket[] | null; error: string | null }> {
|
||||
export async function getTickets(params?: {
|
||||
queue_id?: string;
|
||||
status?: string;
|
||||
q?: string;
|
||||
owner_id?: string;
|
||||
custom_fields?: Record<string, string>;
|
||||
}): Promise<{ data: Ticket[] | null; error: string | null }> {
|
||||
const sp = new URLSearchParams();
|
||||
if (params?.queue_id) sp.set("queue_id", params.queue_id);
|
||||
if (params?.status) sp.set("status", params.status);
|
||||
if (params?.q) sp.set("q", params.q);
|
||||
if (params?.owner_id) sp.set("owner_id", params.owner_id);
|
||||
if (params?.custom_fields) {
|
||||
for (const [fieldId, value] of Object.entries(params.custom_fields)) {
|
||||
if (value) sp.set(`cf.${fieldId}`, value);
|
||||
}
|
||||
}
|
||||
const qs = sp.toString();
|
||||
return request<Ticket[]>(`/tickets${qs ? `?${qs}` : ""}`);
|
||||
}
|
||||
@@ -40,11 +58,16 @@ export async function getTicket(id: number): Promise<{ data: Ticket | null; erro
|
||||
return request<Ticket>(`/tickets/${id}`);
|
||||
}
|
||||
|
||||
export async function createTicket(data: { subject: string; queue_id: string }): Promise<{ data: Ticket | null; error: string | null }> {
|
||||
export async function createTicket(data: {
|
||||
subject: string;
|
||||
queue_id: string;
|
||||
description?: string;
|
||||
custom_fields?: Record<string, string>;
|
||||
}): Promise<{ data: Ticket | null; error: string | null }> {
|
||||
return request<Ticket>("/tickets", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateTicket(id: number, data: { subject?: string; status?: string }): Promise<{ data: UpdateResult | null; error: string | null }> {
|
||||
export async function updateTicket(id: number, data: { subject?: string; status?: string; owner_id?: string | null }): Promise<{ data: UpdateResult | null; error: string | null }> {
|
||||
return request<UpdateResult>(`/tickets/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
@@ -64,18 +87,28 @@ export async function getQueues(): Promise<{ data: Queue[] | null; error: string
|
||||
return request<Queue[]>("/queues");
|
||||
}
|
||||
|
||||
export async function createQueue(data: { name: string; description?: string }): Promise<{ data: Queue | null; error: string | null }> {
|
||||
export async function getUsers(): Promise<{ data: User[] | null; error: string | null }> {
|
||||
return request<User[]>("/users");
|
||||
}
|
||||
|
||||
export async function createQueue(data: { name: string; description?: string | null; lifecycle_id?: string | null }): Promise<{ data: Queue | null; error: string | null }> {
|
||||
return request<Queue>("/queues", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateQueue(id: string, data: { name?: string; description?: string | null; lifecycle_id?: string | null }): Promise<{ data: Queue | null; error: string | null }> {
|
||||
return request<Queue>(`/queues/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function getScrips(): Promise<{ data: Scrip[] | null; error: string | null }> {
|
||||
return request<Scrip[]>("/scrips");
|
||||
}
|
||||
|
||||
export async function createScrip(data: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
queue_id?: string | null;
|
||||
condition_type: string;
|
||||
condition_config?: Record<string, unknown>;
|
||||
action_type: string;
|
||||
action_config?: Record<string, unknown>;
|
||||
template_id?: string | null;
|
||||
@@ -88,8 +121,10 @@ export async function createScrip(data: {
|
||||
|
||||
export async function updateScrip(id: string, data: {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
queue_id?: string | null;
|
||||
condition_type?: string;
|
||||
condition_config?: Record<string, unknown>;
|
||||
action_type?: string;
|
||||
action_config?: Record<string, unknown>;
|
||||
template_id?: string | null;
|
||||
@@ -100,26 +135,98 @@ export async function updateScrip(id: string, data: {
|
||||
return request<Scrip>(`/scrips/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function getTemplates(): Promise<{ data: Template[] | null; error: string | null }> {
|
||||
return request<Template[]>("/templates");
|
||||
}
|
||||
|
||||
export async function createTemplate(data: {
|
||||
name: string;
|
||||
queue_id?: string | null;
|
||||
subject_template: string;
|
||||
body_template: string;
|
||||
}): Promise<{ data: Template | null; error: string | null }> {
|
||||
return request<Template>("/templates", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateTemplate(id: string, data: {
|
||||
name?: string;
|
||||
queue_id?: string | null;
|
||||
subject_template?: string;
|
||||
body_template?: string;
|
||||
}): Promise<{ data: Template | null; error: string | null }> {
|
||||
return request<Template>(`/templates/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function previewTemplate(data: {
|
||||
subject_template: string;
|
||||
body_template: string;
|
||||
ticket_id?: number | null;
|
||||
}): Promise<{ data: TemplatePreview | null; error: string | null }> {
|
||||
return request<TemplatePreview>("/templates/preview", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function getLifecycles(): Promise<{ data: Lifecycle[] | null; error: string | null }> {
|
||||
return request<Lifecycle[]>("/lifecycles");
|
||||
}
|
||||
|
||||
export async function createLifecycle(data: {
|
||||
name: string;
|
||||
definition: Record<string, unknown>;
|
||||
definition: Record<string, unknown> | LifecycleDefinition;
|
||||
}): Promise<{ data: Lifecycle | null; error: string | null }> {
|
||||
return request<Lifecycle>("/lifecycles", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateLifecycle(id: string, data: {
|
||||
name?: string;
|
||||
definition?: Record<string, unknown> | LifecycleDefinition;
|
||||
}): Promise<{ data: Lifecycle | null; error: string | null }> {
|
||||
return request<Lifecycle>(`/lifecycles/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function getCustomFields(): Promise<{ data: CustomField[] | null; error: string | null }> {
|
||||
return request<CustomField[]>("/custom-fields");
|
||||
}
|
||||
|
||||
export async function getQueueCustomFields(queueId: string): Promise<{ data: QueueCustomField[] | null; error: string | null }> {
|
||||
return request<QueueCustomField[]>(`/custom-fields/queues/${queueId}`);
|
||||
}
|
||||
|
||||
export async function assignQueueCustomField(queueId: string, customFieldId: string): Promise<{ data: QueueCustomField | null; error: string | null }> {
|
||||
return request<QueueCustomField>(`/custom-fields/queues/${queueId}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ custom_field_id: customFieldId }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function unassignQueueCustomField(queueId: string, customFieldId: string): Promise<{ data: { ok: boolean } | null; error: string | null }> {
|
||||
return request<{ ok: boolean }>(`/custom-fields/queues/${queueId}/${customFieldId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function updateTicketCustomField(ticketId: number, customFieldId: string, value: string): Promise<{ data: Transaction | null; error: string | null }> {
|
||||
return request<Transaction>(`/tickets/${ticketId}/custom-fields/${customFieldId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ value }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createCustomField(data: {
|
||||
key?: string;
|
||||
name: string;
|
||||
field_type: string;
|
||||
values?: unknown | null;
|
||||
max_values?: number;
|
||||
pattern?: string | null;
|
||||
}): Promise<{ data: CustomField | null; error: string | null }> {
|
||||
return request<CustomField>("/custom-fields", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateCustomField(id: string, data: {
|
||||
key?: string;
|
||||
name?: string;
|
||||
field_type?: string;
|
||||
values?: unknown | null;
|
||||
max_values?: number;
|
||||
pattern?: string | null;
|
||||
}): Promise<{ data: CustomField | null; error: string | null }> {
|
||||
return request<CustomField>(`/custom-fields/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user