* feat: Add routing trace presenters Add domain-specific presenters (SalesforceRoutingTracePresenter, RoutingFormTracePresenter) that format trace steps into human-readable strings, and a core RoutingTracePresenter that delegates to them based on step domain. Includes unit tests for all presenters. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add findByBookingUid to RoutingTraceRepository Add method to look up a routing trace by booking UID, needed by the routing trace presenter tRPC endpoint. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add getRoutingTrace tRPC endpoint Expose routing trace data for a booking via viewer.bookings.getRoutingTrace. Tries the permanent RoutingTrace first (round robin bookings), then falls back to PendingRoutingTrace via the booking's form response relation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add routing trace side sheet to booking list item Add a route icon button on booking list items that came from routing forms. Clicking it opens a side sheet displaying the full routing trace as human-readable steps. Adds RoutingTraceSheet component, store state, and translation key. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: Move routing trace action to dropdown menu Move the routing trace button from a standalone icon on the booking list item into the actions dropdown menu alongside "Report wrong assignment". The RoutingTraceSheet is now rendered from BookingActionsDropdown. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Improve routing trace UI and handle unnamed routes - Redesign RoutingTraceSheet with vertical timeline layout, domain badges, skeleton loading state, and millisecond timestamps - Use Salesforce app-store icon for Salesforce steps - Fall back to "Unnamed route" when route name is missing or matches route ID - Fix dropdown menu icon to git-merge (valid icon, unique in menu) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: Add tests for getRoutingTrace handler and findByBookingUid repository method Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * Abstract functions * Fix build error * Add permission check * fix: Update getRoutingTrace tests to include ctx and mock BookingAccessService Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
122 lines
5.0 KiB
TypeScript
122 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import type React from "react";
|
|
import { createStore } from "zustand";
|
|
|
|
export type BookingActionsStore = {
|
|
// Dialog states
|
|
rejectionDialogIsOpen: boolean;
|
|
chargeCardDialogIsOpen: boolean;
|
|
viewRecordingsDialogIsOpen: boolean;
|
|
meetingSessionDetailsDialogIsOpen: boolean;
|
|
isNoShowDialogOpen: boolean;
|
|
isOpenRescheduleDialog: boolean;
|
|
isOpenReassignDialog: boolean;
|
|
isOpenSetLocationDialog: boolean;
|
|
isOpenAddGuestsDialog: boolean;
|
|
isOpenReportDialog: boolean;
|
|
rerouteDialogIsOpen: boolean;
|
|
isCancelDialogOpen: boolean;
|
|
isOpenWrongAssignmentDialog: boolean;
|
|
isOpenRoutingTraceSheet: boolean;
|
|
|
|
// Dialog setters
|
|
setRejectionDialogIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setChargeCardDialogIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setViewRecordingsDialogIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setMeetingSessionDetailsDialogIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsNoShowDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenRescheduleDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenReassignDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenLocationDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenAddGuestsDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenReportDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setRerouteDialogIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsCancelDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenWrongAssignmentDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
|
setIsOpenRoutingTraceSheet: React.Dispatch<React.SetStateAction<boolean>>;
|
|
};
|
|
|
|
export const createBookingActionsStore = () => {
|
|
return createStore<BookingActionsStore>((set) => ({
|
|
// Initial dialog states
|
|
rejectionDialogIsOpen: false,
|
|
chargeCardDialogIsOpen: false,
|
|
viewRecordingsDialogIsOpen: false,
|
|
meetingSessionDetailsDialogIsOpen: false,
|
|
isNoShowDialogOpen: false,
|
|
isOpenRescheduleDialog: false,
|
|
isOpenReassignDialog: false,
|
|
isOpenSetLocationDialog: false,
|
|
isOpenAddGuestsDialog: false,
|
|
isOpenReportDialog: false,
|
|
rerouteDialogIsOpen: false,
|
|
isCancelDialogOpen: false,
|
|
isOpenWrongAssignmentDialog: false,
|
|
isOpenRoutingTraceSheet: false,
|
|
|
|
// Dialog setters
|
|
setRejectionDialogIsOpen: (isOpen) =>
|
|
set((state) => ({
|
|
rejectionDialogIsOpen: typeof isOpen === "function" ? isOpen(state.rejectionDialogIsOpen) : isOpen,
|
|
})),
|
|
setChargeCardDialogIsOpen: (isOpen) =>
|
|
set((state) => ({
|
|
chargeCardDialogIsOpen: typeof isOpen === "function" ? isOpen(state.chargeCardDialogIsOpen) : isOpen,
|
|
})),
|
|
setViewRecordingsDialogIsOpen: (isOpen) =>
|
|
set((state) => ({
|
|
viewRecordingsDialogIsOpen:
|
|
typeof isOpen === "function" ? isOpen(state.viewRecordingsDialogIsOpen) : isOpen,
|
|
})),
|
|
setMeetingSessionDetailsDialogIsOpen: (isOpen) =>
|
|
set((state) => ({
|
|
meetingSessionDetailsDialogIsOpen:
|
|
typeof isOpen === "function" ? isOpen(state.meetingSessionDetailsDialogIsOpen) : isOpen,
|
|
})),
|
|
setIsNoShowDialogOpen: (isOpen) =>
|
|
set((state) => ({
|
|
isNoShowDialogOpen: typeof isOpen === "function" ? isOpen(state.isNoShowDialogOpen) : isOpen,
|
|
})),
|
|
setIsOpenRescheduleDialog: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenRescheduleDialog: typeof isOpen === "function" ? isOpen(state.isOpenRescheduleDialog) : isOpen,
|
|
})),
|
|
setIsOpenReassignDialog: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenReassignDialog: typeof isOpen === "function" ? isOpen(state.isOpenReassignDialog) : isOpen,
|
|
})),
|
|
setIsOpenLocationDialog: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenSetLocationDialog:
|
|
typeof isOpen === "function" ? isOpen(state.isOpenSetLocationDialog) : isOpen,
|
|
})),
|
|
setIsOpenAddGuestsDialog: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenAddGuestsDialog: typeof isOpen === "function" ? isOpen(state.isOpenAddGuestsDialog) : isOpen,
|
|
})),
|
|
setIsOpenReportDialog: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenReportDialog: typeof isOpen === "function" ? isOpen(state.isOpenReportDialog) : isOpen,
|
|
})),
|
|
setRerouteDialogIsOpen: (isOpen) =>
|
|
set((state) => ({
|
|
rerouteDialogIsOpen: typeof isOpen === "function" ? isOpen(state.rerouteDialogIsOpen) : isOpen,
|
|
})),
|
|
setIsCancelDialogOpen: (isOpen) =>
|
|
set((state) => ({
|
|
isCancelDialogOpen: typeof isOpen === "function" ? isOpen(state.isCancelDialogOpen) : isOpen,
|
|
})),
|
|
setIsOpenWrongAssignmentDialog: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenWrongAssignmentDialog:
|
|
typeof isOpen === "function" ? isOpen(state.isOpenWrongAssignmentDialog) : isOpen,
|
|
})),
|
|
setIsOpenRoutingTraceSheet: (isOpen) =>
|
|
set((state) => ({
|
|
isOpenRoutingTraceSheet:
|
|
typeof isOpen === "function" ? isOpen(state.isOpenRoutingTraceSheet) : isOpen,
|
|
})),
|
|
}));
|
|
};
|