feat: ticket system base implementation

This commit is contained in:
2026-06-19 01:19:01 +05:30
parent a18fff4d6f
commit 8c3d19a429
19 changed files with 1400 additions and 27 deletions

View File

@@ -11,3 +11,4 @@ export * from './user.service';
export * from './client.service';
export * from './plan.service';
export * from './tenant.service';
export * from './ticket.service';

View File

@@ -0,0 +1,115 @@
import axiosClient from '../axios/axios';
import { API_ROUTES } from '@/constants/apiRoutes';
import type {
AssignTicketPayload,
AssignableTicketUsersResponse,
ReviewRepairPayload,
SseTokenResponse,
SubmitRepairPayload,
TicketDetail,
TicketListParams,
TicketListResponse,
} from '@/types';
export const ticketService = {
getTickets: async (
params?: TicketListParams,
): Promise<TicketListResponse> => {
const response = await axiosClient.get<TicketListResponse>(
API_ROUTES.TICKETS.BASE,
{
params: {
skip: params?.skip ?? 0,
limit: params?.limit ?? 10,
status: params?.status,
chainage_id: params?.chainage_id,
},
},
);
return response.data;
},
getTicketDetail: async (ticketId: string): Promise<TicketDetail> => {
const response = await axiosClient.get<TicketDetail>(
API_ROUTES.TICKETS.DETAIL(ticketId),
);
return response.data;
},
getAssignableUsers: async (): Promise<AssignableTicketUsersResponse> => {
const response = await axiosClient.get<AssignableTicketUsersResponse>(
API_ROUTES.TICKETS.ASSIGNABLE_USERS,
);
return response.data;
},
assignTicket: async (
ticketId: string,
payload: AssignTicketPayload,
): Promise<TicketDetail> => {
const response = await axiosClient.post<TicketDetail>(
API_ROUTES.TICKETS.ASSIGN(ticketId),
payload,
);
return response.data;
},
startTicket: async (ticketId: string): Promise<TicketDetail> => {
const response = await axiosClient.post<TicketDetail>(
API_ROUTES.TICKETS.START(ticketId),
);
return response.data;
},
submitRepair: async (
ticketId: string,
payload: SubmitRepairPayload,
): Promise<TicketDetail> => {
const response = await axiosClient.post<TicketDetail>(
API_ROUTES.TICKETS.SUBMIT_REPAIR(ticketId),
payload,
);
return response.data;
},
reviewRepair: async (
ticketId: string,
payload: ReviewRepairPayload,
): Promise<TicketDetail> => {
const response = await axiosClient.post<TicketDetail>(
API_ROUTES.TICKETS.REVIEW(ticketId),
payload,
);
return response.data;
},
closeTicket: async (ticketId: string): Promise<TicketDetail> => {
const response = await axiosClient.post<TicketDetail>(
API_ROUTES.TICKETS.CLOSE(ticketId),
);
return response.data;
},
createTicketEventsToken: async (
ticketId: string,
): Promise<SseTokenResponse> => {
const response = await axiosClient.post<SseTokenResponse>(
API_ROUTES.TICKETS.DETAIL_EVENTS_TOKEN(ticketId),
);
return response.data;
},
createTenantTicketEventsToken: async (): Promise<SseTokenResponse> => {
const response = await axiosClient.post<SseTokenResponse>(
API_ROUTES.TICKET_EVENTS.TENANT_TOKEN,
);
return response.data;
},
createUserEventsToken: async (): Promise<SseTokenResponse> => {
const response = await axiosClient.post<SseTokenResponse>(
API_ROUTES.TICKET_EVENTS.USER_TOKEN,
);
return response.data;
},
};

View File

@@ -0,0 +1 @@
export * from './sse.service';

View File

@@ -0,0 +1,66 @@
import { ENV_CONSTANT } from '@/constants/secrect.constant';
export type SseEventHandler<T = unknown> = (
data: T,
event: MessageEvent,
) => void;
export type SseEventMap<TEvents extends Record<string, unknown>> = {
[EventName in keyof TEvents]: SseEventHandler<TEvents[EventName]>;
};
export interface SseConnection {
source: EventSource;
close: () => void;
}
function getSseUrl(path: string) {
if (/^https?:\/\//i.test(path)) return path;
const baseUrl = ENV_CONSTANT.BASE_API_URL ?? '';
const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
return `${normalizedBase}${normalizedPath}`;
}
function parseEventData(event: MessageEvent) {
if (!event.data) return null;
try {
return JSON.parse(event.data);
} catch {
return event.data;
}
}
export const sseService = {
connect: <TEvents extends Record<string, unknown>>(
path: string,
events: SseEventMap<TEvents>,
): SseConnection => {
const source = new EventSource(getSseUrl(path));
const cleanups: Array<() => void> = [];
Object.entries(events).forEach(([eventName, handler]) => {
const listener = (event: Event) => {
const messageEvent = event as MessageEvent;
(handler as SseEventHandler)(
parseEventData(messageEvent),
messageEvent,
);
};
source.addEventListener(eventName, listener);
cleanups.push(() => source.removeEventListener(eventName, listener));
});
return {
source,
close: () => {
cleanups.forEach((cleanup) => cleanup());
source.close();
},
};
},
};