)}
diff --git a/src/components/messenger/messenger-dashboard.tsx b/src/components/messenger/messenger-dashboard.tsx
index fa56874..33bf1b4 100644
--- a/src/components/messenger/messenger-dashboard.tsx
+++ b/src/components/messenger/messenger-dashboard.tsx
@@ -9,7 +9,7 @@ import { useSidebar } from '@/hooks/useSidebar'
import { MessengerConversations } from './messenger-conversations'
import { MessengerChat } from './messenger-chat'
import { MessengerEmptyState } from './messenger-empty-state'
-import { GET_MY_COUNTERPARTIES } from '@/graphql/queries'
+import { GET_CONVERSATIONS, GET_MY_COUNTERPARTIES } from '@/graphql/queries'
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'
import { MessageCircle } from 'lucide-react'
@@ -26,21 +26,47 @@ interface Organization {
createdAt: string
}
+interface Conversation {
+ id: string
+ counterparty: Organization
+ lastMessage?: {
+ id: string
+ content?: string
+ type?: string
+ senderId: string
+ isRead: boolean
+ createdAt: string
+ }
+ unreadCount: number
+ updatedAt: string
+}
+
export function MessengerDashboard() {
const { getSidebarMargin } = useSidebar()
const [selectedCounterparty, setSelectedCounterparty] = useState
(null)
+ // Загружаем список чатов (conversations) для отображения непрочитанных сообщений
+ const { data: conversationsData, loading: conversationsLoading, refetch: refetchConversations } = useQuery(GET_CONVERSATIONS, {
+ pollInterval: 5000, // Обновляем каждые 5 секунд для получения новых непрочитанных сообщений
+ fetchPolicy: 'cache-and-network',
+ })
+
+ // Также загружаем полный список контрагентов на случай, если с кем-то еще не общались
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
+
+ const conversations: Conversation[] = conversationsData?.conversations || []
const counterparties = counterpartiesData?.myCounterparties || []
const handleSelectCounterparty = (counterpartyId: string) => {
setSelectedCounterparty(counterpartyId)
}
- const selectedCounterpartyData = counterparties.find((cp: Organization) => cp.id === selectedCounterparty)
+ // Найти данные выбранного контрагента (сначала в чатах, потом в общем списке)
+ const selectedCounterpartyData = conversations.find((conv: Conversation) => conv.counterparty.id === selectedCounterparty)?.counterparty ||
+ counterparties.find((cp: Organization) => cp.id === selectedCounterparty)
// Если нет контрагентов, показываем заглушку
- if (!counterpartiesLoading && counterparties.length === 0) {
+ if (!counterpartiesLoading && !conversationsLoading && counterparties.length === 0) {
return (
@@ -74,10 +100,12 @@ export function MessengerDashboard() {
>
diff --git a/src/graphql/mutations.ts b/src/graphql/mutations.ts
index 412d5d5..011ecc6 100644
--- a/src/graphql/mutations.ts
+++ b/src/graphql/mutations.ts
@@ -603,6 +603,14 @@ export const CREATE_SUPPLY = gql`
name
description
price
+ quantity
+ unit
+ category
+ status
+ date
+ supplier
+ minStock
+ currentStock
imageUrl
createdAt
updatedAt
@@ -621,6 +629,14 @@ export const UPDATE_SUPPLY = gql`
name
description
price
+ quantity
+ unit
+ category
+ status
+ date
+ supplier
+ minStock
+ currentStock
imageUrl
createdAt
updatedAt
diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts
index c6969b0..94c6f9c 100644
--- a/src/graphql/queries.ts
+++ b/src/graphql/queries.ts
@@ -72,6 +72,14 @@ export const GET_MY_SUPPLIES = gql`
name
description
price
+ quantity
+ unit
+ category
+ status
+ date
+ supplier
+ minStock
+ currentStock
imageUrl
createdAt
updatedAt
diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts
index 500bcd3..c82fa99 100644
--- a/src/graphql/resolvers.ts
+++ b/src/graphql/resolvers.ts
@@ -533,9 +533,81 @@ export const resolvers = {
throw new GraphQLError("У пользователя нет организации");
}
- // TODO: Здесь будет логика получения списка чатов
- // Пока возвращаем пустой массив, так как таблица сообщений еще не создана
- return [];
+ // Получаем всех контрагентов
+ const counterparties = await prisma.counterparty.findMany({
+ where: { organizationId: currentUser.organization.id },
+ include: {
+ counterparty: {
+ include: {
+ users: true,
+ },
+ },
+ },
+ });
+
+ // Для каждого контрагента получаем последнее сообщение и количество непрочитанных
+ const conversations = await Promise.all(
+ counterparties.map(async (cp) => {
+ const counterpartyId = cp.counterparty.id;
+
+ // Последнее сообщение с этим контрагентом
+ const lastMessage = await prisma.message.findFirst({
+ where: {
+ OR: [
+ {
+ senderOrganizationId: currentUser.organization!.id,
+ receiverOrganizationId: counterpartyId,
+ },
+ {
+ senderOrganizationId: counterpartyId,
+ receiverOrganizationId: currentUser.organization!.id,
+ },
+ ],
+ },
+ include: {
+ sender: true,
+ senderOrganization: {
+ include: {
+ users: true,
+ },
+ },
+ receiverOrganization: {
+ include: {
+ users: true,
+ },
+ },
+ },
+ orderBy: { createdAt: "desc" },
+ });
+
+ // Количество непрочитанных сообщений от этого контрагента
+ const unreadCount = await prisma.message.count({
+ where: {
+ senderOrganizationId: counterpartyId,
+ receiverOrganizationId: currentUser.organization!.id,
+ isRead: false,
+ },
+ });
+
+ // Если есть сообщения с этим контрагентом, включаем его в список
+ if (lastMessage) {
+ return {
+ id: `${currentUser.organization!.id}-${counterpartyId}`,
+ counterparty: cp.counterparty,
+ lastMessage,
+ unreadCount,
+ updatedAt: lastMessage.createdAt,
+ };
+ }
+
+ return null;
+ })
+ );
+
+ // Фильтруем null значения и сортируем по времени последнего сообщения
+ return conversations
+ .filter((conv) => conv !== null)
+ .sort((a, b) => new Date(b!.updatedAt).getTime() - new Date(a!.updatedAt).getTime());
},
// Мои услуги
@@ -2410,8 +2482,34 @@ export const resolvers = {
});
}
- // TODO: Здесь будет логика обновления статуса сообщений
- // Пока возвращаем успешный ответ
+ const currentUser = await prisma.user.findUnique({
+ where: { id: context.user.id },
+ include: { organization: true },
+ });
+
+ if (!currentUser?.organization) {
+ throw new GraphQLError("У пользователя нет организации");
+ }
+
+ // conversationId имеет формат "currentOrgId-counterpartyId"
+ const [, counterpartyId] = args.conversationId.split('-');
+
+ if (!counterpartyId) {
+ throw new GraphQLError("Неверный ID беседы");
+ }
+
+ // Помечаем все непрочитанные сообщения от контрагента как прочитанные
+ await prisma.message.updateMany({
+ where: {
+ senderOrganizationId: counterpartyId,
+ receiverOrganizationId: currentUser.organization.id,
+ isRead: false,
+ },
+ data: {
+ isRead: true,
+ },
+ });
+
return true;
},
@@ -2594,6 +2692,14 @@ export const resolvers = {
name: string;
description?: string;
price: number;
+ quantity: number;
+ unit: string;
+ category: string;
+ status: string;
+ date: string;
+ supplier: string;
+ minStock: number;
+ currentStock: number;
imageUrl?: string;
};
},
@@ -2627,7 +2733,14 @@ export const resolvers = {
name: args.input.name,
description: args.input.description,
price: args.input.price,
- quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
+ quantity: args.input.quantity,
+ unit: args.input.unit,
+ category: args.input.category,
+ status: args.input.status,
+ date: new Date(args.input.date),
+ supplier: args.input.supplier,
+ minStock: args.input.minStock,
+ currentStock: args.input.currentStock,
imageUrl: args.input.imageUrl,
organizationId: currentUser.organization.id,
},
@@ -2657,6 +2770,14 @@ export const resolvers = {
name: string;
description?: string;
price: number;
+ quantity: number;
+ unit: string;
+ category: string;
+ status: string;
+ date: string;
+ supplier: string;
+ minStock: number;
+ currentStock: number;
imageUrl?: string;
};
},
@@ -2696,7 +2817,14 @@ export const resolvers = {
name: args.input.name,
description: args.input.description,
price: args.input.price,
- quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
+ quantity: args.input.quantity,
+ unit: args.input.unit,
+ category: args.input.category,
+ status: args.input.status,
+ date: new Date(args.input.date),
+ supplier: args.input.supplier,
+ minStock: args.input.minStock,
+ currentStock: args.input.currentStock,
imageUrl: args.input.imageUrl,
},
include: { organization: true },
@@ -2912,9 +3040,37 @@ export const resolvers = {
},
});
+ // Создаем расходники на основе заказанных товаров
+ const suppliesData = args.input.items.map((item) => {
+ const product = products.find((p) => p.id === item.productId)!;
+ const productWithCategory = supplyOrder.items.find(
+ (orderItem) => orderItem.productId === item.productId
+ )?.product;
+
+ return {
+ name: product.name,
+ description: product.description || `Заказано у ${partner.name}`,
+ price: product.price,
+ quantity: item.quantity,
+ unit: "шт",
+ category: productWithCategory?.category?.name || "Упаковка",
+ status: "in-transit", // Статус "в пути" так как заказ только создан
+ date: new Date(args.input.deliveryDate),
+ supplier: partner.name || partner.fullName || "Не указан",
+ minStock: Math.round(item.quantity * 0.1), // 10% от заказанного как минимальный остаток
+ currentStock: 0, // Пока товар не пришел
+ organizationId: currentUser.organization!.id,
+ };
+ });
+
+ // Создаем расходники
+ await prisma.supply.createMany({
+ data: suppliesData,
+ });
+
return {
success: true,
- message: "Заказ поставки создан успешно",
+ message: `Заказ поставки создан успешно! Добавлено ${suppliesData.length} расходников в каталог.`,
order: supplyOrder,
};
} catch (error) {
@@ -3000,7 +3156,7 @@ export const resolvers = {
weight: args.input.weight,
dimensions: args.input.dimensions,
material: args.input.material,
- images: args.input.images || [],
+ images: JSON.stringify(args.input.images || []),
mainImage: args.input.mainImage,
isActive: args.input.isActive ?? true,
organizationId: currentUser.organization.id,
@@ -3111,7 +3267,7 @@ export const resolvers = {
weight: args.input.weight,
dimensions: args.input.dimensions,
material: args.input.material,
- images: args.input.images || [],
+ images: args.input.images ? JSON.stringify(args.input.images) : undefined,
mainImage: args.input.mainImage,
isActive: args.input.isActive ?? true,
},
@@ -4213,6 +4369,25 @@ export const resolvers = {
},
},
+ Product: {
+ images: (parent: { images: unknown }) => {
+ // Если images это строка JSON, парсим её в массив
+ if (typeof parent.images === 'string') {
+ try {
+ return JSON.parse(parent.images);
+ } catch {
+ return [];
+ }
+ }
+ // Если это уже массив, возвращаем как есть
+ if (Array.isArray(parent.images)) {
+ return parent.images;
+ }
+ // Иначе возвращаем пустой массив
+ return [];
+ },
+ },
+
Message: {
type: (parent: { type?: string | null }) => {
return parent.type || "TEXT";
diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts
index a464a08..0829742 100644
--- a/src/graphql/typedefs.ts
+++ b/src/graphql/typedefs.ts
@@ -444,6 +444,14 @@ export const typeDefs = gql`
name: String!
description: String
price: Float!
+ quantity: Int!
+ unit: String
+ category: String
+ status: String
+ date: DateTime!
+ supplier: String
+ minStock: Int
+ currentStock: Int
imageUrl: String
createdAt: DateTime!
updatedAt: DateTime!
@@ -454,6 +462,14 @@ export const typeDefs = gql`
name: String!
description: String
price: Float!
+ quantity: Int!
+ unit: String!
+ category: String!
+ status: String!
+ date: DateTime!
+ supplier: String!
+ minStock: Int!
+ currentStock: Int!
imageUrl: String
}