Files
clientsflow/frontend/app/composables/useDocuments.ts
Ruslan Bakiev d892d0c604 refactor: distribute types from crm-types.ts to owning composables
Each composable now owns its types and exports them. Other composables
import types from the owning composable. Deleted centralized crm-types.ts.

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

204 lines
6.6 KiB
TypeScript

import { ref, computed, watch, watchEffect, type ComputedRef } from "vue";
import { useQuery, useMutation } from "@vue/apollo-composable";
import {
DocumentsQueryDocument,
CreateWorkspaceDocumentDocument,
DeleteWorkspaceDocumentDocument,
} from "~~/graphql/generated";
import { formatDocumentScope } from "~/composables/useWorkspaceDocuments";
export type DocumentSortMode = "updatedAt" | "title" | "owner";
export type WorkspaceDocument = {
id: string;
title: string;
type: "Regulation" | "Playbook" | "Policy" | "Template";
owner: string;
scope: string;
updatedAt: string;
summary: string;
body: string;
};
function safeTrim(value: unknown) { return String(value ?? "").trim(); }
export function useDocuments(opts: { apolloAuthReady: ComputedRef<boolean> }) {
const { result: documentsResult, refetch: refetchDocuments } = useQuery(
DocumentsQueryDocument,
null,
{ enabled: opts.apolloAuthReady },
);
const { mutate: doCreateWorkspaceDocument } = useMutation(CreateWorkspaceDocumentDocument, {
refetchQueries: [{ query: DocumentsQueryDocument }],
});
const { mutate: doDeleteWorkspaceDocument } = useMutation(DeleteWorkspaceDocumentDocument, {
refetchQueries: [{ query: DocumentsQueryDocument }],
});
const documents = ref<WorkspaceDocument[]>([]);
const documentSearch = ref("");
const documentSortMode = ref<DocumentSortMode>("updatedAt");
const selectedDocumentId = ref(documents.value[0]?.id ?? "");
const documentDeletingId = ref("");
const documentSortOptions: Array<{ value: DocumentSortMode; label: string }> = [
{ value: "updatedAt", label: "Updated" },
{ value: "title", label: "Title" },
{ value: "owner", label: "Owner" },
];
watch(() => documentsResult.value?.documents, (v) => {
if (v) documents.value = v as WorkspaceDocument[];
}, { immediate: true });
const filteredDocuments = computed(() => {
const query = documentSearch.value.trim().toLowerCase();
const list = documents.value
.filter((item) => {
if (!query) return true;
const haystack = [item.title, item.summary, item.owner, formatDocumentScope(item.scope), item.body].join(" ").toLowerCase();
return haystack.includes(query);
})
.sort((a, b) => {
if (documentSortMode.value === "title") return a.title.localeCompare(b.title);
if (documentSortMode.value === "owner") return a.owner.localeCompare(b.owner);
return b.updatedAt.localeCompare(a.updatedAt);
});
return list;
});
watchEffect(() => {
if (!filteredDocuments.value.length) {
selectedDocumentId.value = "";
return;
}
if (!filteredDocuments.value.some((item) => item.id === selectedDocumentId.value)) {
const first = filteredDocuments.value[0];
if (first) selectedDocumentId.value = first.id;
}
});
const selectedDocument = computed(() => documents.value.find((item) => item.id === selectedDocumentId.value));
function updateSelectedDocumentBody(value: string) {
if (!selectedDocument.value) return;
selectedDocument.value.body = value;
}
function openDocumentsTab(opts2: { setTab: (tab: string) => void; syncPath: (push: boolean) => void }, push = false) {
opts2.setTab("documents");
if (!selectedDocumentId.value && filteredDocuments.value.length) {
const first = filteredDocuments.value[0];
if (first) selectedDocumentId.value = first.id;
}
opts2.syncPath(push);
}
async function deleteWorkspaceDocumentById(
documentIdInput: string,
clientTimelineItems: { value: ClientTimelineItem[] },
) {
const documentId = safeTrim(documentIdInput);
if (!documentId) return;
if (documentDeletingId.value === documentId) return;
const target = documents.value.find((doc) => doc.id === documentId);
const targetLabel = safeTrim(target?.title) || "this document";
if (process.client && !window.confirm(`Delete ${targetLabel}?`)) return;
documentDeletingId.value = documentId;
try {
await doDeleteWorkspaceDocument({ id: documentId });
documents.value = documents.value.filter((doc) => doc.id !== documentId);
clientTimelineItems.value = clientTimelineItems.value.filter((item) => {
const isDocumentEntry = String(item.contentType).toLowerCase() === "document";
if (!isDocumentEntry) return true;
return item.contentId !== documentId && item.document?.id !== documentId;
});
if (selectedDocumentId.value === documentId) {
selectedDocumentId.value = "";
}
} finally {
if (documentDeletingId.value === documentId) {
documentDeletingId.value = "";
}
}
}
async function createCommDocument(
threadContact: { id: string; contact: string } | undefined,
draftText: string,
commDocumentForm: { value: { title: string } },
authDisplayName: string,
additionalCallbacks: {
buildScope: (contactId: string, contactName: string) => string;
onSuccess: (created: WorkspaceDocument | null) => void;
},
) {
if (!threadContact) return false;
const summary = draftText.trim();
if (!summary) return false;
const title = safeTrim(commDocumentForm.value.title)
|| buildCommDocumentTitle(summary, threadContact.contact);
const scope = additionalCallbacks.buildScope(threadContact.id, threadContact.contact);
const body = summary;
try {
const res = await doCreateWorkspaceDocument({
input: {
title,
owner: authDisplayName,
scope,
summary,
body,
},
});
const created = res?.data?.createWorkspaceDocument;
if (created) {
documents.value = [created as WorkspaceDocument, ...documents.value.filter((doc) => doc.id !== created.id)];
selectedDocumentId.value = created.id;
} else {
selectedDocumentId.value = "";
}
additionalCallbacks.onSuccess((created as WorkspaceDocument) ?? null);
return true;
} catch {
return false;
}
}
function buildCommDocumentTitle(text: string, contact: string) {
const cleaned = text.replace(/\s+/g, " ").trim();
if (cleaned) {
const sentence = cleaned.split(/[.!?\n]/)[0]?.trim() ?? "";
if (sentence) return sentence.slice(0, 120);
}
return `Документ для ${contact}`;
}
return {
documents,
documentSearch,
documentSortMode,
selectedDocumentId,
documentDeletingId,
documentSortOptions,
selectedDocument,
filteredDocuments,
updateSelectedDocumentBody,
createCommDocument,
buildCommDocumentTitle,
deleteWorkspaceDocumentById,
openDocumentsTab,
refetchDocuments,
};
}