IKHSAN
EN Read in EnglishID

opendesk

Publik
Branch
main
Commit terakhir
08d9f3328 Oktober 2026

useTicket.ts

Buka raw
import type { TicketAnalytics } from "@/components/ticket-agent/analytics/types";
import type { CommentExtras } from "@/components/ticket-agent/timeline/TimelineCommentRow.vue";
import { useNotificationStore } from "@/stores/notification";
import { __ } from "@/translation";
import type {
  DocumentResource,
  RecentSimilarTicket,
  Resource,
  TicketAssignee,
  TicketContact,
} from "@/types";
import type { ODTicket } from "@/types/doctypes";
import { createDocumentResource, createResource, toast } from "frappe-ui";
import { reactive } from "vue";

interface MapValue {
  ticket: DocumentResource<ODTicket>;
  assignees: Resource<TicketAssignee[]>;
  contact: Resource<TicketContact>;
  recentSimilarTickets: Resource<RecentSimilarTicket>;
  analytics: Resource<TicketAnalytics>;
  // shared by every timeline tab instance, fetched once per ticket
  calls: Resource<Record<string, any>[]>;
  commentExtras: Resource<TicketCommentExtras>;
  // lent by the mounted timeline; see registerTicketFeed
  reloadFeed?: () => void;
}

export interface TicketCommentExtras {
  comments: Record<string, CommentExtras>;
  // oldest first, so a pin's position is its "N:" in the pinned bar
  pinned_comments: string[];
}

export interface PinnedComment {
  name: string;
  content: string;
}

const ticketMap: Record<string, MapValue> = reactive({});

export const useTicket = (ticketId: string): MapValue => {
  if (!ticketMap[ticketId]) {
    ticketMap[ticketId] = {
      ticket: createDocumentResource<ODTicket>({
        doctype: "OD Ticket",
        name: ticketId,
        whitelistedMethods: {
          markSeen: {
            method: "mark_seen",
            onSuccess: (cleared: number) =>
              cleared && useNotificationStore().controller.reload(),
          },
        },
        setValue: {
          onSuccess: () => {
            toast.success(__("Ticket updated successfully."));
          },
          onError: (error) => {
            const msg = error.exc_type
              ? (error.messages || error.message || []).join(", ")
              : error.message;
            toast.error(msg);
          },
        },
      }),
      assignees: createResource({
        url: "opendesk.opendesk.doctype.od_ticket.api.get_ticket_assignees",
        params: { ticket: ticketId },
        auto: true,
      }),
      contact: createResource({
        url: "opendesk.opendesk.doctype.od_ticket.api.get_ticket_contact",
        params: { ticket: ticketId },
        auto: true,
      }),
      recentSimilarTickets: createResource({
        url: "opendesk.opendesk.doctype.od_ticket.api.get_recent_similar_tickets",
        params: { ticket: ticketId },
        auto: true,
      }),
      // fetched by the analytics tab, not on ticket open
      analytics: createResource({
        url: "opendesk.api.ticket_analytics.get_ticket_analytics",
        params: { ticket: ticketId },
        cache: ["Ticket", ticketId, "analytics"],
      }),
      calls: createResource({
        url: "opendesk.api.timeline.get_ticket_calls",
        params: { ticket: ticketId },
        auto: true,
      }),
      commentExtras: createResource({
        url: "opendesk.api.timeline.get_comment_extras",
        params: { ticket: ticketId },
        auto: true,
      }),
    };
  }

  return ticketMap[ticketId];
};

export function reloadTicket(ticketId: string) {
  const ticketData = ticketMap[ticketId];
  if (!ticketData) return;
  ticketData.ticket.reload();
  ticketData.assignees.reload();
}

// The timeline owns its feed through useActivityTimeline, so anything outside it
// (a saved reply applying actions, say) reloads through the mounted component.
export function registerTicketFeed(ticketId: string, reload: () => void) {
  const ticketData = ticketMap[ticketId];
  if (!ticketData) return () => {};
  ticketData.reloadFeed = reload;
  // identity check: on ticket switch the new instance mounts before the old unmounts
  return () => {
    if (ticketData.reloadFeed === reload) delete ticketData.reloadFeed;
  };
}

export function reloadTicketFeed(ticketId: string) {
  ticketMap[ticketId]?.reloadFeed?.();
}

// Refresh a ticket that may have gone stale
export function revalidateTicket(ticketId: string | number) {
  const ticketData = ticketMap[ticketId];
  if (ticketData?.ticket.get.fetched && !ticketData.ticket.get.loading) {
    reloadTicket(ticketId as string);
  }
}
byte
4.393
baris
134
Jenis
ts
Commit terakhir
28 Oktober 2026