mirror of
https://github.com/hanzoai/helpdesk.git
synced 2026-10-12 16:56:02 +00:00
refactor: seed agent availability from session
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
import { createResource, toast } from "frappe-ui";
|
||||
import { computed } from "vue";
|
||||
import { __ } from "@/translation";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const availability = createResource({
|
||||
url: "helpdesk.api.agent.get_my_availability",
|
||||
});
|
||||
|
||||
const setAvailability = createResource({
|
||||
url: "helpdesk.api.agent.set_my_availability",
|
||||
onSuccess: () => { availability.reload();
|
||||
toast.success(__("Status updated successfully."));
|
||||
},
|
||||
});
|
||||
|
||||
export function useAvailability() {
|
||||
const authStore = useAuthStore();
|
||||
if (authStore.isAgent && !availability.fetched) {
|
||||
availability.fetch();
|
||||
}
|
||||
|
||||
const currentStatus = computed(() => availability.data?.availability || "");
|
||||
const options = computed(() => availability.data?.options || []);
|
||||
|
||||
function setStatus(status: string) {
|
||||
if (status === currentStatus.value) return;
|
||||
setAvailability.submit({ availability: status });
|
||||
}
|
||||
|
||||
return { currentStatus, options, setStatus };
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { globalStore } from "@/stores/globalStore";
|
||||
import { __ } from "@/translation";
|
||||
import { HDAgentStatus } from "@/types/doctypes";
|
||||
import { createListResource } from "frappe-ui";
|
||||
import { createListResource, createResource, toast } from "frappe-ui";
|
||||
import { defineStore } from "pinia";
|
||||
import { reactive } from "vue";
|
||||
import { computed, reactive, watch } from "vue";
|
||||
|
||||
interface LiveAvailability {
|
||||
availability: string;
|
||||
@@ -35,6 +37,9 @@ const dotColorMap: Record<string, string> = {
|
||||
const defaultColor = "bg-surface-gray-5";
|
||||
|
||||
export const useAgentStatusStore = defineStore("agentStatus", () => {
|
||||
// Set only for sessions that have an HD Agent record; null otherwise.
|
||||
const myAgentName = window.agent;
|
||||
|
||||
const statuses = createListResource({
|
||||
doctype: "HD Agent Status",
|
||||
cache: ["HD Agent Status", "list"],
|
||||
@@ -44,19 +49,63 @@ export const useAgentStatusStore = defineStore("agentStatus", () => {
|
||||
auto: true,
|
||||
});
|
||||
|
||||
// Live availability pushed over the socket when an agent changes their
|
||||
// status (helpdesk.api.agent.set_my_availability). Keyed by HD Agent name,
|
||||
// it overrides the value fetched when a list first loaded.
|
||||
// Live availability keyed by HD Agent name. Seeded by fetches and kept current
|
||||
// over the socket — set_my_availability broadcasts to every client, so this is
|
||||
// the single source of truth for "who is what right now", including ourselves.
|
||||
const liveStatuses = reactive<Record<string, LiveAvailability>>({});
|
||||
|
||||
function applyLive(agent: string, availability?: string, changedOn?: string) {
|
||||
if (!agent || !availability) return;
|
||||
liveStatuses[agent] = { availability, changedOn: changedOn ?? "" };
|
||||
}
|
||||
|
||||
const { $socket } = globalStore();
|
||||
$socket.on("agent_availability_updated", (data: AvailabilityEvent) => {
|
||||
liveStatuses[data.agent] = {
|
||||
availability: data.availability,
|
||||
changedOn: data.availability_changed_on,
|
||||
};
|
||||
$socket.on("agent_availability_updated", (data: AvailabilityEvent) =>
|
||||
applyLive(data.agent, data.availability, data.availability_changed_on)
|
||||
);
|
||||
|
||||
// Seed our own status from the session payload (auth.get_user already resolves
|
||||
// the agent), then let the socket and the optimistic write keep it current — no
|
||||
// dedicated fetch and no reload.
|
||||
const auth = useAuthStore();
|
||||
watch(
|
||||
() => auth.availability,
|
||||
(availability) =>
|
||||
applyLive(myAgentName ?? "", availability, auth.availabilityChangedOn),
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const setMyAvailability = createResource({
|
||||
url: "helpdesk.api.agent.set_my_availability",
|
||||
onSuccess: () => toast.success(__("Status updated successfully.")),
|
||||
onError: () => toast.error(__("Could not update status.")),
|
||||
});
|
||||
|
||||
const myStatus = computed(
|
||||
() => (myAgentName && liveStatuses[myAgentName]?.availability) || ""
|
||||
);
|
||||
|
||||
// Selectable statuses, derived from the same list the presence dots use so the
|
||||
// menu and the dots never disagree and we avoid a second server round-trip.
|
||||
const statusOptions = computed<string[]>(() =>
|
||||
(statuses.data ?? [])
|
||||
.filter((s: HDAgentStatus) => s.enable)
|
||||
.map((s: HDAgentStatus) => s.agent_status)
|
||||
);
|
||||
|
||||
function setMyStatus(status: string) {
|
||||
if (!myAgentName || !status || status === myStatus.value) return;
|
||||
// Optimistic: update immediately, then roll back if the server rejects it.
|
||||
// changedOn is left empty — it is never rendered for our own dot and the
|
||||
// socket echo overwrites it with the authoritative server timestamp.
|
||||
const previous = liveStatuses[myAgentName];
|
||||
applyLive(myAgentName, status);
|
||||
setMyAvailability.submit({ availability: status }).catch(() => {
|
||||
if (previous) liveStatuses[myAgentName] = previous;
|
||||
else delete liveStatuses[myAgentName];
|
||||
});
|
||||
}
|
||||
|
||||
function getStatus(name: string): HDAgentStatus | undefined {
|
||||
return statuses.data?.find((s: HDAgentStatus) => s.agent_status === name);
|
||||
}
|
||||
@@ -66,5 +115,13 @@ export const useAgentStatusStore = defineStore("agentStatus", () => {
|
||||
return (color && dotColorMap[color]) || defaultColor;
|
||||
}
|
||||
|
||||
return { statuses, liveStatuses, getStatus, statusColor };
|
||||
return {
|
||||
statuses,
|
||||
liveStatuses,
|
||||
myStatus,
|
||||
statusOptions,
|
||||
setMyStatus,
|
||||
getStatus,
|
||||
statusColor,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -44,6 +44,12 @@ export const useAuthStore = defineStore("auth", () => {
|
||||
);
|
||||
const userName: ComputedRef<string> = computed(() => user__.value.user_name);
|
||||
const username: ComputedRef<string> = computed(() => user__.value.username);
|
||||
const availability: ComputedRef<string> = computed(
|
||||
() => user__.value.availability || ""
|
||||
);
|
||||
const availabilityChangedOn: ComputedRef<string> = computed(
|
||||
() => user__.value.availability_changed_on || ""
|
||||
);
|
||||
const timezone: ComputedRef<string> = computed(() => user__.value.time_zone);
|
||||
const userTeams: ComputedRef<string[]> = computed(
|
||||
() => user__.value.user_teams
|
||||
@@ -94,6 +100,8 @@ export const useAuthStore = defineStore("auth", () => {
|
||||
userImage,
|
||||
userName,
|
||||
username,
|
||||
availability,
|
||||
availabilityChangedOn,
|
||||
timezone,
|
||||
userTeams,
|
||||
language,
|
||||
|
||||
@@ -30,38 +30,6 @@ def sent_invites(emails: list[str], send_welcome_mail_to_user: bool = True):
|
||||
return
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@agent_only
|
||||
def get_availability_options() -> list[str]:
|
||||
return frappe.get_all(
|
||||
"HD Agent Status",
|
||||
filters={"enable": 1},
|
||||
order_by="`tabHD Agent Status`.status_order asc",
|
||||
pluck="agent_status",
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@agent_only
|
||||
def get_my_availability() -> dict:
|
||||
name = get_agent_name()
|
||||
values = (
|
||||
frappe.db.get_value(
|
||||
"HD Agent",
|
||||
name,
|
||||
["availability", "availability_changed_on"],
|
||||
as_dict=True,
|
||||
)
|
||||
if name
|
||||
else {}
|
||||
) or {}
|
||||
return {
|
||||
"availability": values.get("availability"),
|
||||
"availability_changed_on": values.get("availability_changed_on"),
|
||||
"options": get_availability_options(),
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@agent_only
|
||||
def set_my_availability(availability: str) -> dict:
|
||||
|
||||
+14
-1
@@ -28,8 +28,19 @@ def get_user():
|
||||
is_admin = "System Manager" in roles or "Administrator" in roles
|
||||
|
||||
is_agent = _is_agent()
|
||||
has_agent_record = bool(get_agent_name())
|
||||
agent_name = get_agent_name()
|
||||
has_agent_record = bool(agent_name)
|
||||
has_desk_access = is_agent or is_admin
|
||||
availability = (
|
||||
frappe.db.get_value(
|
||||
"HD Agent",
|
||||
agent_name,
|
||||
["availability", "availability_changed_on"],
|
||||
as_dict=True,
|
||||
)
|
||||
if agent_name
|
||||
else None
|
||||
) or {}
|
||||
user_image = user.user_image
|
||||
user_first_name = user.first_name
|
||||
user_name = user.full_name
|
||||
@@ -56,6 +67,8 @@ def get_user():
|
||||
"time_zone": user.time_zone,
|
||||
"user_teams": user_team_names,
|
||||
"language": language,
|
||||
"availability": availability.get("availability"),
|
||||
"availability_changed_on": availability.get("availability_changed_on"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user