refactor(graphql): replace dashboard query with resource queries
This commit is contained in:
@@ -10,7 +10,14 @@ import CrmPilotSidebar from "~~/app/components/workspace/pilot/CrmPilotSidebar.v
|
||||
import CrmChangeReviewOverlay from "~~/app/components/workspace/review/CrmChangeReviewOverlay.vue";
|
||||
import meQuery from "~~/graphql/operations/me.graphql?raw";
|
||||
import chatMessagesQuery from "~~/graphql/operations/chat-messages.graphql?raw";
|
||||
import dashboardQuery from "~~/graphql/operations/dashboard.graphql?raw";
|
||||
import contactsQuery from "~~/graphql/operations/contacts.graphql?raw";
|
||||
import communicationsQuery from "~~/graphql/operations/communications.graphql?raw";
|
||||
import contactInboxesQuery from "~~/graphql/operations/contact-inboxes.graphql?raw";
|
||||
import calendarQuery from "~~/graphql/operations/calendar.graphql?raw";
|
||||
import dealsQuery from "~~/graphql/operations/deals.graphql?raw";
|
||||
import feedQuery from "~~/graphql/operations/feed.graphql?raw";
|
||||
import pinsQuery from "~~/graphql/operations/pins.graphql?raw";
|
||||
import documentsQuery from "~~/graphql/operations/documents.graphql?raw";
|
||||
import getClientTimelineQuery from "~~/graphql/operations/get-client-timeline.graphql?raw";
|
||||
import logoutMutation from "~~/graphql/operations/logout.graphql?raw";
|
||||
import logPilotNoteMutation from "~~/graphql/operations/log-pilot-note.graphql?raw";
|
||||
@@ -982,27 +989,34 @@ async function logout() {
|
||||
}
|
||||
|
||||
async function refreshCrmData() {
|
||||
const data = await gqlFetch<{
|
||||
dashboard: {
|
||||
contacts: Contact[];
|
||||
communications: CommItem[];
|
||||
contactInboxes: ContactInbox[];
|
||||
calendar: CalendarEvent[];
|
||||
deals: Deal[];
|
||||
feed: FeedCard[];
|
||||
pins: CommPin[];
|
||||
documents: WorkspaceDocument[];
|
||||
};
|
||||
}>(dashboardQuery);
|
||||
const [
|
||||
contactsData,
|
||||
communicationsData,
|
||||
contactInboxesData,
|
||||
calendarData,
|
||||
dealsData,
|
||||
feedData,
|
||||
pinsData,
|
||||
documentsData,
|
||||
] = await Promise.all([
|
||||
gqlFetch<{ contacts: Contact[] }>(contactsQuery),
|
||||
gqlFetch<{ communications: CommItem[] }>(communicationsQuery),
|
||||
gqlFetch<{ contactInboxes: ContactInbox[] }>(contactInboxesQuery),
|
||||
gqlFetch<{ calendar: CalendarEvent[] }>(calendarQuery),
|
||||
gqlFetch<{ deals: Deal[] }>(dealsQuery),
|
||||
gqlFetch<{ feed: FeedCard[] }>(feedQuery),
|
||||
gqlFetch<{ pins: CommPin[] }>(pinsQuery),
|
||||
gqlFetch<{ documents: WorkspaceDocument[] }>(documentsQuery),
|
||||
]);
|
||||
|
||||
contacts.value = data.dashboard.contacts ?? [];
|
||||
commItems.value = data.dashboard.communications ?? [];
|
||||
contactInboxes.value = data.dashboard.contactInboxes ?? [];
|
||||
calendarEvents.value = data.dashboard.calendar ?? [];
|
||||
deals.value = data.dashboard.deals ?? [];
|
||||
feedCards.value = data.dashboard.feed ?? [];
|
||||
commPins.value = data.dashboard.pins ?? [];
|
||||
documents.value = data.dashboard.documents ?? [];
|
||||
contacts.value = contactsData.contacts ?? [];
|
||||
commItems.value = communicationsData.communications ?? [];
|
||||
contactInboxes.value = contactInboxesData.contactInboxes ?? [];
|
||||
calendarEvents.value = calendarData.calendar ?? [];
|
||||
deals.value = dealsData.deals ?? [];
|
||||
feedCards.value = feedData.feed ?? [];
|
||||
commPins.value = pinsData.pins ?? [];
|
||||
documents.value = documentsData.documents ?? [];
|
||||
|
||||
// Derive channels per contact from communication items.
|
||||
const byName = new Map<string, Set<string>>();
|
||||
|
||||
14
frontend/graphql/operations/calendar.graphql
Normal file
14
frontend/graphql/operations/calendar.graphql
Normal file
@@ -0,0 +1,14 @@
|
||||
query CalendarQuery {
|
||||
calendar {
|
||||
id
|
||||
title
|
||||
start
|
||||
end
|
||||
contact
|
||||
note
|
||||
isArchived
|
||||
createdAt
|
||||
archiveNote
|
||||
archivedAt
|
||||
}
|
||||
}
|
||||
19
frontend/graphql/operations/communications.graphql
Normal file
19
frontend/graphql/operations/communications.graphql
Normal file
@@ -0,0 +1,19 @@
|
||||
query CommunicationsQuery {
|
||||
communications {
|
||||
id
|
||||
at
|
||||
contactId
|
||||
contact
|
||||
contactInboxId
|
||||
sourceExternalId
|
||||
sourceTitle
|
||||
channel
|
||||
kind
|
||||
direction
|
||||
text
|
||||
audioUrl
|
||||
duration
|
||||
transcript
|
||||
deliveryStatus
|
||||
}
|
||||
}
|
||||
13
frontend/graphql/operations/contact-inboxes.graphql
Normal file
13
frontend/graphql/operations/contact-inboxes.graphql
Normal file
@@ -0,0 +1,13 @@
|
||||
query ContactInboxesQuery {
|
||||
contactInboxes {
|
||||
id
|
||||
contactId
|
||||
contactName
|
||||
channel
|
||||
sourceExternalId
|
||||
title
|
||||
isHidden
|
||||
lastMessageAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
13
frontend/graphql/operations/contacts.graphql
Normal file
13
frontend/graphql/operations/contacts.graphql
Normal file
@@ -0,0 +1,13 @@
|
||||
query ContactsQuery {
|
||||
contacts {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
company
|
||||
country
|
||||
location
|
||||
channels
|
||||
lastContactAt
|
||||
description
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
query DashboardQuery {
|
||||
dashboard {
|
||||
contacts {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
company
|
||||
country
|
||||
location
|
||||
channels
|
||||
lastContactAt
|
||||
description
|
||||
}
|
||||
communications {
|
||||
id
|
||||
at
|
||||
contactId
|
||||
contact
|
||||
contactInboxId
|
||||
sourceExternalId
|
||||
sourceTitle
|
||||
channel
|
||||
kind
|
||||
direction
|
||||
text
|
||||
audioUrl
|
||||
duration
|
||||
transcript
|
||||
deliveryStatus
|
||||
}
|
||||
contactInboxes {
|
||||
id
|
||||
contactId
|
||||
contactName
|
||||
channel
|
||||
sourceExternalId
|
||||
title
|
||||
isHidden
|
||||
lastMessageAt
|
||||
updatedAt
|
||||
}
|
||||
calendar {
|
||||
id
|
||||
title
|
||||
start
|
||||
end
|
||||
contact
|
||||
note
|
||||
isArchived
|
||||
createdAt
|
||||
archiveNote
|
||||
archivedAt
|
||||
}
|
||||
deals {
|
||||
id
|
||||
contact
|
||||
title
|
||||
company
|
||||
stage
|
||||
amount
|
||||
nextStep
|
||||
summary
|
||||
currentStepId
|
||||
steps {
|
||||
id
|
||||
title
|
||||
description
|
||||
status
|
||||
dueAt
|
||||
order
|
||||
completedAt
|
||||
}
|
||||
}
|
||||
feed {
|
||||
id
|
||||
at
|
||||
contact
|
||||
text
|
||||
proposal {
|
||||
title
|
||||
details
|
||||
key
|
||||
}
|
||||
decision
|
||||
decisionNote
|
||||
}
|
||||
pins {
|
||||
id
|
||||
contact
|
||||
text
|
||||
}
|
||||
documents {
|
||||
id
|
||||
title
|
||||
type
|
||||
owner
|
||||
scope
|
||||
updatedAt
|
||||
summary
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
22
frontend/graphql/operations/deals.graphql
Normal file
22
frontend/graphql/operations/deals.graphql
Normal file
@@ -0,0 +1,22 @@
|
||||
query DealsQuery {
|
||||
deals {
|
||||
id
|
||||
contact
|
||||
title
|
||||
company
|
||||
stage
|
||||
amount
|
||||
nextStep
|
||||
summary
|
||||
currentStepId
|
||||
steps {
|
||||
id
|
||||
title
|
||||
description
|
||||
status
|
||||
dueAt
|
||||
order
|
||||
completedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
12
frontend/graphql/operations/documents.graphql
Normal file
12
frontend/graphql/operations/documents.graphql
Normal file
@@ -0,0 +1,12 @@
|
||||
query DocumentsQuery {
|
||||
documents {
|
||||
id
|
||||
title
|
||||
type
|
||||
owner
|
||||
scope
|
||||
updatedAt
|
||||
summary
|
||||
body
|
||||
}
|
||||
}
|
||||
15
frontend/graphql/operations/feed.graphql
Normal file
15
frontend/graphql/operations/feed.graphql
Normal file
@@ -0,0 +1,15 @@
|
||||
query FeedQuery {
|
||||
feed {
|
||||
id
|
||||
at
|
||||
contact
|
||||
text
|
||||
proposal {
|
||||
title
|
||||
details
|
||||
key
|
||||
}
|
||||
decision
|
||||
decisionNote
|
||||
}
|
||||
}
|
||||
7
frontend/graphql/operations/pins.graphql
Normal file
7
frontend/graphql/operations/pins.graphql
Normal file
@@ -0,0 +1,7 @@
|
||||
query PinsQuery {
|
||||
pins {
|
||||
id
|
||||
contact
|
||||
text
|
||||
}
|
||||
}
|
||||
@@ -441,31 +441,21 @@ async function getChatMessages(auth: AuthContext | null) {
|
||||
});
|
||||
}
|
||||
|
||||
async function getDashboard(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const from = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||
const to = new Date(Date.now() + 1000 * 60 * 60 * 24 * 60);
|
||||
async function getHiddenInboxIds(teamId: string, userId: string) {
|
||||
const hiddenPrefRows = await prisma.contactInboxPreference.findMany({
|
||||
where: {
|
||||
teamId: ctx.teamId,
|
||||
userId: ctx.userId,
|
||||
isHidden: true,
|
||||
},
|
||||
where: { teamId, userId, isHidden: true },
|
||||
select: { contactInboxId: true },
|
||||
});
|
||||
const hiddenInboxIds = hiddenPrefRows.map((row) => row.contactInboxId);
|
||||
const messageWhere = visibleMessageWhere(hiddenInboxIds);
|
||||
return hiddenPrefRows.map((row) => row.contactInboxId);
|
||||
}
|
||||
|
||||
const [
|
||||
contactsRaw,
|
||||
communicationsRaw,
|
||||
contactInboxesRaw,
|
||||
calendarRaw,
|
||||
dealsRaw,
|
||||
feedRaw,
|
||||
pinsRaw,
|
||||
documentsRaw,
|
||||
] = await Promise.all([
|
||||
async function getContacts(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const hiddenInboxIds = await getHiddenInboxIds(ctx.teamId, ctx.userId);
|
||||
const messageWhere = visibleMessageWhere(hiddenInboxIds);
|
||||
const hiddenInboxIdSet = new Set(hiddenInboxIds);
|
||||
|
||||
const [contactsRaw, contactInboxesRaw, communicationsRaw] = await Promise.all([
|
||||
prisma.contact.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: {
|
||||
@@ -475,66 +465,77 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 500,
|
||||
}),
|
||||
prisma.contactInbox.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
select: { id: true, contactId: true, channel: true },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 5000,
|
||||
}),
|
||||
prisma.contactMessage.findMany({
|
||||
where: {
|
||||
contact: { teamId: ctx.teamId },
|
||||
...(messageWhere ?? {}),
|
||||
},
|
||||
select: { contactId: true, channel: true },
|
||||
orderBy: { occurredAt: "asc" },
|
||||
take: 2000,
|
||||
include: {
|
||||
contact: { select: { id: true, name: true } },
|
||||
contactInbox: { select: { id: true, sourceExternalId: true, title: true } },
|
||||
},
|
||||
}),
|
||||
prisma.contactInbox.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
include: {
|
||||
contact: { select: { name: true } },
|
||||
messages: {
|
||||
where: messageWhere,
|
||||
select: { occurredAt: true },
|
||||
orderBy: { occurredAt: "desc" },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
take: 5000,
|
||||
}),
|
||||
prisma.calendarEvent.findMany({
|
||||
where: { teamId: ctx.teamId, startsAt: { gte: from, lte: to } },
|
||||
include: { contact: { select: { name: true } } },
|
||||
orderBy: { startsAt: "asc" },
|
||||
take: 500,
|
||||
}),
|
||||
prisma.deal.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: {
|
||||
contact: { select: { name: true, company: true } },
|
||||
steps: { orderBy: [{ order: "asc" }, { createdAt: "asc" }] },
|
||||
},
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 500,
|
||||
}),
|
||||
prisma.feedCard.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: { contact: { select: { name: true } } },
|
||||
orderBy: { happenedAt: "desc" },
|
||||
take: 200,
|
||||
}),
|
||||
prisma.contactPin.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: { contact: { select: { name: true } } },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 500,
|
||||
}),
|
||||
prisma.workspaceDocument.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 200,
|
||||
}),
|
||||
]);
|
||||
|
||||
const channelsByContactId = new Map<string, Set<string>>();
|
||||
const totalInboxesByContactId = new Map<string, number>();
|
||||
const visibleInboxesByContactId = new Map<string, number>();
|
||||
|
||||
for (const inbox of contactInboxesRaw) {
|
||||
totalInboxesByContactId.set(inbox.contactId, (totalInboxesByContactId.get(inbox.contactId) ?? 0) + 1);
|
||||
if (hiddenInboxIdSet.has(inbox.id)) continue;
|
||||
visibleInboxesByContactId.set(inbox.contactId, (visibleInboxesByContactId.get(inbox.contactId) ?? 0) + 1);
|
||||
if (!channelsByContactId.has(inbox.contactId)) channelsByContactId.set(inbox.contactId, new Set());
|
||||
channelsByContactId.get(inbox.contactId)?.add(mapChannel(inbox.channel));
|
||||
}
|
||||
|
||||
for (const item of communicationsRaw) {
|
||||
if (!channelsByContactId.has(item.contactId)) channelsByContactId.set(item.contactId, new Set());
|
||||
channelsByContactId.get(item.contactId)?.add(mapChannel(item.channel));
|
||||
}
|
||||
|
||||
return contactsRaw
|
||||
.filter((c) => {
|
||||
const total = totalInboxesByContactId.get(c.id) ?? 0;
|
||||
if (total === 0) return true;
|
||||
return (visibleInboxesByContactId.get(c.id) ?? 0) > 0;
|
||||
})
|
||||
.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
avatar: c.avatarUrl ?? "",
|
||||
company: c.company ?? "",
|
||||
country: c.country ?? "",
|
||||
location: c.location ?? "",
|
||||
channels: Array.from(channelsByContactId.get(c.id) ?? []),
|
||||
lastContactAt: c.messages[0]?.occurredAt?.toISOString?.() ?? c.updatedAt.toISOString(),
|
||||
description: c.note?.content ?? "",
|
||||
}));
|
||||
}
|
||||
|
||||
async function getCommunications(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const hiddenInboxIds = await getHiddenInboxIds(ctx.teamId, ctx.userId);
|
||||
const messageWhere = visibleMessageWhere(hiddenInboxIds);
|
||||
|
||||
const communicationsRaw = await prisma.contactMessage.findMany({
|
||||
where: {
|
||||
contact: { teamId: ctx.teamId },
|
||||
...(messageWhere ?? {}),
|
||||
},
|
||||
orderBy: { occurredAt: "asc" },
|
||||
take: 2000,
|
||||
include: {
|
||||
contact: { select: { id: true, name: true } },
|
||||
contactInbox: { select: { id: true, sourceExternalId: true, title: true } },
|
||||
},
|
||||
});
|
||||
|
||||
let omniMessagesRaw: Array<{
|
||||
id: string;
|
||||
contactId: string;
|
||||
@@ -558,10 +559,7 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
where: {
|
||||
teamId: ctx.teamId,
|
||||
contactId: { in: contactIds },
|
||||
occurredAt: {
|
||||
gte: fromOccurredAt,
|
||||
lte: toOccurredAt,
|
||||
},
|
||||
occurredAt: { gte: fromOccurredAt, lte: toOccurredAt },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
@@ -579,46 +577,6 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
});
|
||||
}
|
||||
|
||||
const hiddenInboxIdSet = new Set(hiddenInboxIds);
|
||||
const channelsByContactId = new Map<string, Set<string>>();
|
||||
const totalInboxesByContactId = new Map<string, number>();
|
||||
const visibleInboxesByContactId = new Map<string, number>();
|
||||
|
||||
for (const inbox of contactInboxesRaw) {
|
||||
totalInboxesByContactId.set(inbox.contactId, (totalInboxesByContactId.get(inbox.contactId) ?? 0) + 1);
|
||||
if (hiddenInboxIdSet.has(inbox.id)) continue;
|
||||
visibleInboxesByContactId.set(inbox.contactId, (visibleInboxesByContactId.get(inbox.contactId) ?? 0) + 1);
|
||||
if (!channelsByContactId.has(inbox.contactId)) {
|
||||
channelsByContactId.set(inbox.contactId, new Set());
|
||||
}
|
||||
channelsByContactId.get(inbox.contactId)?.add(mapChannel(inbox.channel));
|
||||
}
|
||||
|
||||
for (const item of communicationsRaw) {
|
||||
if (!channelsByContactId.has(item.contactId)) {
|
||||
channelsByContactId.set(item.contactId, new Set());
|
||||
}
|
||||
channelsByContactId.get(item.contactId)?.add(mapChannel(item.channel));
|
||||
}
|
||||
|
||||
const contacts = contactsRaw
|
||||
.filter((c) => {
|
||||
const total = totalInboxesByContactId.get(c.id) ?? 0;
|
||||
if (total === 0) return true;
|
||||
return (visibleInboxesByContactId.get(c.id) ?? 0) > 0;
|
||||
})
|
||||
.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
avatar: c.avatarUrl ?? "",
|
||||
company: c.company ?? "",
|
||||
country: c.country ?? "",
|
||||
location: c.location ?? "",
|
||||
channels: Array.from(channelsByContactId.get(c.id) ?? []),
|
||||
lastContactAt: c.messages[0]?.occurredAt?.toISOString?.() ?? c.updatedAt.toISOString(),
|
||||
description: c.note?.content ?? "",
|
||||
}));
|
||||
|
||||
const omniByKey = new Map<string, typeof omniMessagesRaw>();
|
||||
for (const row of omniMessagesRaw) {
|
||||
const normalizedText = extractOmniNormalizedText(row.rawJson, row.text);
|
||||
@@ -664,7 +622,7 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
return best.status;
|
||||
};
|
||||
|
||||
const communications = communicationsRaw.map((m) => ({
|
||||
return communicationsRaw.map((m) => ({
|
||||
id: m.id,
|
||||
at: m.occurredAt.toISOString(),
|
||||
contactId: m.contactId,
|
||||
@@ -681,21 +639,55 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
transcript: Array.isArray(m.transcriptJson) ? ((m.transcriptJson as any) as string[]) : [],
|
||||
deliveryStatus: resolveDeliveryStatus(m),
|
||||
}));
|
||||
}
|
||||
|
||||
const contactInboxes = contactInboxesRaw
|
||||
.map((inbox) => ({
|
||||
id: inbox.id,
|
||||
contactId: inbox.contactId,
|
||||
contactName: inbox.contact.name,
|
||||
channel: mapChannel(inbox.channel),
|
||||
sourceExternalId: inbox.sourceExternalId,
|
||||
title: inbox.title ?? "",
|
||||
isHidden: hiddenInboxIdSet.has(inbox.id),
|
||||
lastMessageAt: inbox.messages[0]?.occurredAt?.toISOString?.() ?? "",
|
||||
updatedAt: inbox.updatedAt.toISOString(),
|
||||
}));
|
||||
async function getContactInboxes(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const hiddenInboxIds = await getHiddenInboxIds(ctx.teamId, ctx.userId);
|
||||
const messageWhere = visibleMessageWhere(hiddenInboxIds);
|
||||
const hiddenInboxIdSet = new Set(hiddenInboxIds);
|
||||
|
||||
const calendar = calendarRaw.map((e) => ({
|
||||
const contactInboxesRaw = await prisma.contactInbox.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
include: {
|
||||
contact: { select: { name: true } },
|
||||
messages: {
|
||||
where: messageWhere,
|
||||
select: { occurredAt: true },
|
||||
orderBy: { occurredAt: "desc" },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
take: 5000,
|
||||
});
|
||||
|
||||
return contactInboxesRaw.map((inbox) => ({
|
||||
id: inbox.id,
|
||||
contactId: inbox.contactId,
|
||||
contactName: inbox.contact.name,
|
||||
channel: mapChannel(inbox.channel),
|
||||
sourceExternalId: inbox.sourceExternalId,
|
||||
title: inbox.title ?? "",
|
||||
isHidden: hiddenInboxIdSet.has(inbox.id),
|
||||
lastMessageAt: inbox.messages[0]?.occurredAt?.toISOString?.() ?? "",
|
||||
updatedAt: inbox.updatedAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
async function getCalendar(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const from = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
|
||||
const to = new Date(Date.now() + 1000 * 60 * 60 * 24 * 60);
|
||||
|
||||
const calendarRaw = await prisma.calendarEvent.findMany({
|
||||
where: { teamId: ctx.teamId, startsAt: { gte: from, lte: to } },
|
||||
include: { contact: { select: { name: true } } },
|
||||
orderBy: { startsAt: "asc" },
|
||||
take: 500,
|
||||
});
|
||||
|
||||
return calendarRaw.map((e) => ({
|
||||
id: e.id,
|
||||
title: e.title,
|
||||
start: e.startsAt.toISOString(),
|
||||
@@ -707,8 +699,21 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
archiveNote: e.archiveNote ?? "",
|
||||
archivedAt: e.archivedAt?.toISOString() ?? "",
|
||||
}));
|
||||
}
|
||||
|
||||
const deals = dealsRaw.map((d) => ({
|
||||
async function getDeals(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const dealsRaw = await prisma.deal.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: {
|
||||
contact: { select: { name: true, company: true } },
|
||||
steps: { orderBy: [{ order: "asc" }, { createdAt: "asc" }] },
|
||||
},
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 500,
|
||||
});
|
||||
|
||||
return dealsRaw.map((d) => ({
|
||||
id: d.id,
|
||||
contact: d.contact.name,
|
||||
title: d.title,
|
||||
@@ -728,8 +733,18 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
completedAt: step.completedAt?.toISOString() ?? "",
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
const feed = feedRaw.map((c) => ({
|
||||
async function getFeed(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const feedRaw = await prisma.feedCard.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: { contact: { select: { name: true } } },
|
||||
orderBy: { happenedAt: "desc" },
|
||||
take: 200,
|
||||
});
|
||||
|
||||
return feedRaw.map((c) => ({
|
||||
id: c.id,
|
||||
at: c.happenedAt.toISOString(),
|
||||
contact: c.contact?.name ?? "",
|
||||
@@ -742,14 +757,33 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
decision: c.decision === "ACCEPTED" ? "accepted" : c.decision === "REJECTED" ? "rejected" : "pending",
|
||||
decisionNote: c.decisionNote ?? "",
|
||||
}));
|
||||
}
|
||||
|
||||
const pins = pinsRaw.map((p) => ({
|
||||
async function getPins(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const pinsRaw = await prisma.contactPin.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
include: { contact: { select: { name: true } } },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 500,
|
||||
});
|
||||
|
||||
return pinsRaw.map((p) => ({
|
||||
id: p.id,
|
||||
contact: p.contact.name,
|
||||
text: p.text,
|
||||
}));
|
||||
}
|
||||
|
||||
const documents = documentsRaw.map((d) => ({
|
||||
async function getDocuments(auth: AuthContext | null) {
|
||||
const ctx = requireAuth(auth);
|
||||
const documentsRaw = await prisma.workspaceDocument.findMany({
|
||||
where: { teamId: ctx.teamId },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 200,
|
||||
});
|
||||
|
||||
return documentsRaw.map((d) => ({
|
||||
id: d.id,
|
||||
title: d.title,
|
||||
type: d.type,
|
||||
@@ -759,17 +793,6 @@ async function getDashboard(auth: AuthContext | null) {
|
||||
summary: d.summary,
|
||||
body: d.body,
|
||||
}));
|
||||
|
||||
return {
|
||||
contacts,
|
||||
communications,
|
||||
contactInboxes,
|
||||
calendar,
|
||||
deals,
|
||||
feed,
|
||||
pins,
|
||||
documents,
|
||||
};
|
||||
}
|
||||
|
||||
async function getClientTimeline(auth: AuthContext | null, contactIdInput: string, limitInput?: number) {
|
||||
@@ -1779,7 +1802,14 @@ export const crmGraphqlSchema = buildSchema(`
|
||||
me: MePayload!
|
||||
chatMessages: [PilotMessage!]!
|
||||
chatConversations: [Conversation!]!
|
||||
dashboard: DashboardPayload!
|
||||
contacts: [Contact!]!
|
||||
communications: [CommItem!]!
|
||||
contactInboxes: [ContactInbox!]!
|
||||
calendar: [CalendarEvent!]!
|
||||
deals: [Deal!]!
|
||||
feed: [FeedCard!]!
|
||||
pins: [CommPin!]!
|
||||
documents: [WorkspaceDocument!]!
|
||||
getClientTimeline(contactId: ID!, limit: Int): [ClientTimelineItem!]!
|
||||
}
|
||||
|
||||
@@ -1917,17 +1947,6 @@ export const crmGraphqlSchema = buildSchema(`
|
||||
at: String!
|
||||
}
|
||||
|
||||
type DashboardPayload {
|
||||
contacts: [Contact!]!
|
||||
communications: [CommItem!]!
|
||||
contactInboxes: [ContactInbox!]!
|
||||
calendar: [CalendarEvent!]!
|
||||
deals: [Deal!]!
|
||||
feed: [FeedCard!]!
|
||||
pins: [CommPin!]!
|
||||
documents: [WorkspaceDocument!]!
|
||||
}
|
||||
|
||||
type ClientTimelineItem {
|
||||
id: ID!
|
||||
contactId: String!
|
||||
@@ -2056,7 +2075,14 @@ export const crmGraphqlRoot = {
|
||||
me: async (_args: unknown, context: GraphQLContext) => getAuthPayload(context.auth),
|
||||
chatMessages: async (_args: unknown, context: GraphQLContext) => getChatMessages(context.auth),
|
||||
chatConversations: async (_args: unknown, context: GraphQLContext) => getChatConversations(context.auth),
|
||||
dashboard: async (_args: unknown, context: GraphQLContext) => getDashboard(context.auth),
|
||||
contacts: async (_args: unknown, context: GraphQLContext) => getContacts(context.auth),
|
||||
communications: async (_args: unknown, context: GraphQLContext) => getCommunications(context.auth),
|
||||
contactInboxes: async (_args: unknown, context: GraphQLContext) => getContactInboxes(context.auth),
|
||||
calendar: async (_args: unknown, context: GraphQLContext) => getCalendar(context.auth),
|
||||
deals: async (_args: unknown, context: GraphQLContext) => getDeals(context.auth),
|
||||
feed: async (_args: unknown, context: GraphQLContext) => getFeed(context.auth),
|
||||
pins: async (_args: unknown, context: GraphQLContext) => getPins(context.auth),
|
||||
documents: async (_args: unknown, context: GraphQLContext) => getDocuments(context.auth),
|
||||
getClientTimeline: async (
|
||||
args: { contactId: string; limit?: number },
|
||||
context: GraphQLContext,
|
||||
|
||||
Reference in New Issue
Block a user