Files
Ruslan Bakiev a4d8d81de9 refactor: decompose CrmWorkspaceApp.vue into 15 composables
Split the 6000+ line monolithic component into modular composables:
- crm-types.ts: shared types and utility functions
- useAuth, useContacts, useContactInboxes, useCalendar, useDeals,
  useDocuments, useFeed, useTimeline, usePilotChat, useCallAudio,
  usePins, useChangeReview, useCrmRealtime, useWorkspaceRouting
CrmWorkspaceApp.vue is now a thin orchestrator (~2500 lines) that
wires composables together with glue code, keeping template and
styles intact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:05:01 +07:00

206 lines
7.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref, computed, watch } from "vue";
import { useQuery, useMutation } from "@vue/apollo-composable";
import { MeQueryDocument, LogoutMutationDocument } from "~~/graphql/generated";
type TelegramConnectStatus =
| "not_connected"
| "pending_link"
| "pending_business_connection"
| "connected"
| "disabled"
| "no_reply_rights";
type TelegramConnectionSummary = {
businessConnectionId: string;
isEnabled: boolean | null;
canReply: boolean | null;
updatedAt: string;
};
export function useAuth() {
// -------------------------------------------------------------------------
// Auth state
// -------------------------------------------------------------------------
const authMe = ref<{
user: { id: string; phone: string; name: string };
team: { id: string; name: string };
conversation: { id: string; title: string };
} | null>(null);
const authResolved = ref(false);
const apolloAuthReady = computed(() => !!authMe.value);
// -------------------------------------------------------------------------
// Apollo: Me query
// -------------------------------------------------------------------------
const { result: meResult, refetch: refetchMe, loading: meLoading } = useQuery(
MeQueryDocument,
null,
{ fetchPolicy: "network-only" },
);
watch(() => meResult.value?.me, (me) => {
if (me) authMe.value = me as typeof authMe.value;
}, { immediate: true });
// -------------------------------------------------------------------------
// Apollo: Logout mutation
// -------------------------------------------------------------------------
const { mutate: doLogout } = useMutation(LogoutMutationDocument);
// -------------------------------------------------------------------------
// loadMe / logout
// -------------------------------------------------------------------------
async function loadMe() {
const result = await refetchMe();
const me = result?.data?.me;
if (me) authMe.value = me as typeof authMe.value;
}
async function logout() {
await doLogout();
authMe.value = null;
telegramConnectStatus.value = "not_connected";
telegramConnections.value = [];
telegramConnectUrl.value = "";
if (process.client) {
await navigateTo("/login", { replace: true });
}
}
// -------------------------------------------------------------------------
// Telegram connect state
// -------------------------------------------------------------------------
const telegramConnectStatus = ref<TelegramConnectStatus>("not_connected");
const telegramConnectStatusLoading = ref(false);
const telegramConnectBusy = ref(false);
const telegramConnectUrl = ref("");
const telegramConnections = ref<TelegramConnectionSummary[]>([]);
const telegramConnectNotice = ref("");
const telegramStatusLabel = computed(() => {
if (telegramConnectStatusLoading.value) return "Checking";
if (telegramConnectStatus.value === "connected") return "Connected";
if (telegramConnectStatus.value === "pending_link") return "Pending link";
if (telegramConnectStatus.value === "pending_business_connection") return "Waiting business connect";
if (telegramConnectStatus.value === "disabled") return "Disabled";
if (telegramConnectStatus.value === "no_reply_rights") return "No reply rights";
return "Not connected";
});
const telegramStatusBadgeClass = computed(() => {
if (telegramConnectStatus.value === "connected") return "badge-success";
if (telegramConnectStatus.value === "pending_link" || telegramConnectStatus.value === "pending_business_connection") return "badge-warning";
if (telegramConnectStatus.value === "disabled" || telegramConnectStatus.value === "no_reply_rights") return "badge-error";
return "badge-ghost";
});
// -------------------------------------------------------------------------
// Telegram connect functions
// -------------------------------------------------------------------------
async function loadTelegramConnectStatus() {
if (!authMe.value) {
telegramConnectStatus.value = "not_connected";
telegramConnections.value = [];
telegramConnectUrl.value = "";
return;
}
telegramConnectStatusLoading.value = true;
try {
const result = await $fetch<{
ok: boolean;
status: TelegramConnectStatus;
connections?: TelegramConnectionSummary[];
}>("/api/omni/telegram/business/connect/status", {
method: "GET",
});
telegramConnectStatus.value = result?.status ?? "not_connected";
telegramConnections.value = result?.connections ?? [];
} catch {
telegramConnectStatus.value = "not_connected";
telegramConnections.value = [];
} finally {
telegramConnectStatusLoading.value = false;
}
}
async function startTelegramBusinessConnect() {
if (telegramConnectBusy.value) return;
telegramConnectBusy.value = true;
try {
const result = await $fetch<{
ok: boolean;
status: TelegramConnectStatus;
connectUrl: string;
expiresAt: string;
}>("/api/omni/telegram/business/connect/start", { method: "POST" });
telegramConnectStatus.value = result?.status ?? "pending_link";
telegramConnectUrl.value = String(result?.connectUrl ?? "").trim();
if (telegramConnectUrl.value && process.client) {
window.location.href = telegramConnectUrl.value;
}
} catch {
telegramConnectStatus.value = "not_connected";
} finally {
telegramConnectBusy.value = false;
await loadTelegramConnectStatus();
}
}
async function completeTelegramBusinessConnectFromToken(token: string) {
const t = String(token || "").trim();
if (!t) return;
try {
const result = await $fetch<{
ok: boolean;
status: string;
businessConnectionId?: string;
}>("/api/omni/telegram/business/connect/complete", {
method: "POST",
body: { token: t },
});
if (result?.ok) {
telegramConnectStatus.value = "connected";
telegramConnectNotice.value = "Telegram успешно привязан.";
await loadTelegramConnectStatus();
return;
}
if (result?.status === "awaiting_telegram_start") {
telegramConnectNotice.value = "Сначала нажмите Start в Telegram, затем нажмите кнопку в боте снова.";
} else if (result?.status === "invalid_or_expired_token") {
telegramConnectNotice.value = "Ссылка привязки истекла. Нажмите Connect в CRM заново.";
} else {
telegramConnectNotice.value = "Не удалось завершить привязку. Запустите Connect заново.";
}
} catch {
telegramConnectNotice.value = "Ошибка завершения привязки. Попробуйте снова.";
}
}
return {
authMe,
authResolved,
apolloAuthReady,
meLoading,
loadMe,
logout,
// telegram
telegramConnectStatus,
telegramConnectStatusLoading,
telegramConnectBusy,
telegramConnectUrl,
telegramConnections,
telegramConnectNotice,
telegramStatusLabel,
telegramStatusBadgeClass,
loadTelegramConnectStatus,
startTelegramBusinessConnect,
completeTelegramBusinessConnectFromToken,
};
}