Добавлено новое поле типа товара в модель Product и соответствующие изменения в компонентах, формах и GraphQL запросах. Реализована логика выбора типа товара в интерфейсе, обновлены резолверы и типы для поддержки нового поля. Улучшена обработка данных и интерфейс для отображения типа товара.
This commit is contained in:
@ -232,6 +232,7 @@ model Product {
|
||||
description String?
|
||||
price Decimal @db.Decimal(12, 2)
|
||||
quantity Int @default(0)
|
||||
type ProductType @default(PRODUCT)
|
||||
categoryId String?
|
||||
brand String?
|
||||
color String?
|
||||
@ -438,6 +439,11 @@ enum WildberriesSupplyStatus {
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum ProductType {
|
||||
PRODUCT
|
||||
CONSUMABLE
|
||||
}
|
||||
|
||||
model Logistics {
|
||||
id String @id @default(cuid())
|
||||
fromLocation String
|
||||
|
@ -18,6 +18,7 @@ interface Product {
|
||||
description: string
|
||||
price: number
|
||||
quantity: number
|
||||
type: 'PRODUCT' | 'CONSUMABLE'
|
||||
category: { id: string; name: string } | null
|
||||
brand: string
|
||||
color: string
|
||||
@ -180,13 +181,26 @@ export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) {
|
||||
|
||||
{/* Дополнительная информация */}
|
||||
<div className="space-y-1">
|
||||
{product.category && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Тип товара */}
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${
|
||||
product.type === 'PRODUCT'
|
||||
? 'bg-blue-500/20 text-blue-300 border-blue-400/30'
|
||||
: 'bg-orange-500/20 text-orange-300 border-orange-400/30'
|
||||
}`}
|
||||
>
|
||||
{product.type === 'PRODUCT' ? 'Товар' : 'Расходник'}
|
||||
</Badge>
|
||||
|
||||
{/* Категория */}
|
||||
{product.category && (
|
||||
<Badge variant="outline" className="glass-secondary text-white/60 border-white/20 text-xs">
|
||||
{product.category.name}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{product.brand && (
|
||||
|
@ -20,6 +20,7 @@ interface Product {
|
||||
description: string
|
||||
price: number
|
||||
quantity: number
|
||||
type: 'PRODUCT' | 'CONSUMABLE'
|
||||
category: { id: string; name: string } | null
|
||||
brand: string
|
||||
color: string
|
||||
@ -45,6 +46,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
description: product?.description || '',
|
||||
price: product?.price || 0,
|
||||
quantity: product?.quantity || 0,
|
||||
type: product?.type || 'PRODUCT' as 'PRODUCT' | 'CONSUMABLE',
|
||||
categoryId: product?.category?.id || 'none',
|
||||
brand: product?.brand || '',
|
||||
color: product?.color || '',
|
||||
@ -183,6 +185,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
description: formData.description || undefined,
|
||||
price: formData.price,
|
||||
quantity: formData.quantity,
|
||||
type: formData.type,
|
||||
categoryId: formData.categoryId && formData.categoryId !== 'none' ? formData.categoryId : undefined,
|
||||
brand: formData.brand || undefined,
|
||||
color: formData.color || undefined,
|
||||
@ -258,6 +261,28 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Тип <span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.type}
|
||||
onValueChange={(value) => handleInputChange('type', value)}
|
||||
>
|
||||
<SelectTrigger className="glass-input text-white h-10">
|
||||
<SelectValue placeholder="Выберите тип" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card">
|
||||
<SelectItem value="PRODUCT" className="text-white">
|
||||
Товар
|
||||
</SelectItem>
|
||||
<SelectItem value="CONSUMABLE" className="text-white">
|
||||
Расходник
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
|
@ -20,6 +20,7 @@ interface Product {
|
||||
description: string
|
||||
price: number
|
||||
quantity: number
|
||||
type: 'PRODUCT' | 'CONSUMABLE'
|
||||
category: { id: string; name: string } | null
|
||||
brand: string
|
||||
color: string
|
||||
|
@ -804,6 +804,7 @@ export const CREATE_PRODUCT = gql`
|
||||
description
|
||||
price
|
||||
quantity
|
||||
type
|
||||
category {
|
||||
id
|
||||
name
|
||||
@ -836,6 +837,7 @@ export const UPDATE_PRODUCT = gql`
|
||||
description
|
||||
price
|
||||
quantity
|
||||
type
|
||||
category {
|
||||
id
|
||||
name
|
||||
|
@ -111,6 +111,7 @@ export const GET_MY_PRODUCTS = gql`
|
||||
description
|
||||
price
|
||||
quantity
|
||||
type
|
||||
category {
|
||||
id
|
||||
name
|
||||
|
@ -202,6 +202,10 @@ export const resolvers = {
|
||||
JSON: JSONScalar,
|
||||
DateTime: DateTimeScalar,
|
||||
|
||||
Product: {
|
||||
type: (parent: any) => parent.type || "PRODUCT",
|
||||
},
|
||||
|
||||
Query: {
|
||||
me: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
@ -1248,13 +1252,13 @@ export const resolvers = {
|
||||
try {
|
||||
const categories = await prisma.category.findMany({
|
||||
orderBy: {
|
||||
name: 'asc'
|
||||
}
|
||||
name: "asc",
|
||||
},
|
||||
});
|
||||
return categories;
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения категорий:', error);
|
||||
throw new GraphQLError('Не удалось получить категории');
|
||||
console.error("Ошибка получения категорий:", error);
|
||||
throw new GraphQLError("Не удалось получить категории");
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -1652,8 +1656,8 @@ export const resolvers = {
|
||||
|
||||
console.log(`🔍 Validating ${marketplace} API key:`, {
|
||||
keyLength: apiKey.length,
|
||||
keyPreview: apiKey.substring(0, 20) + '...',
|
||||
validateOnly
|
||||
keyPreview: apiKey.substring(0, 20) + "...",
|
||||
validateOnly,
|
||||
});
|
||||
|
||||
// Валидируем API ключ
|
||||
@ -1666,7 +1670,10 @@ export const resolvers = {
|
||||
console.log(`✅ Validation result for ${marketplace}:`, validationResult);
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
console.log(`❌ Validation failed for ${marketplace}:`, validationResult.message);
|
||||
console.log(
|
||||
`❌ Validation failed for ${marketplace}:`,
|
||||
validationResult.message
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
message: validationResult.message,
|
||||
@ -3396,6 +3403,7 @@ export const resolvers = {
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
type?: "PRODUCT" | "CONSUMABLE";
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
@ -3453,6 +3461,7 @@ export const resolvers = {
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
quantity: args.input.quantity,
|
||||
type: args.input.type || "PRODUCT",
|
||||
categoryId: args.input.categoryId,
|
||||
brand: args.input.brand,
|
||||
color: args.input.color,
|
||||
@ -3496,6 +3505,7 @@ export const resolvers = {
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
type?: "PRODUCT" | "CONSUMABLE";
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
@ -3564,6 +3574,7 @@ export const resolvers = {
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
quantity: args.input.quantity,
|
||||
...(args.input.type && { type: args.input.type }),
|
||||
categoryId: args.input.categoryId,
|
||||
brand: args.input.brand,
|
||||
color: args.input.color,
|
||||
@ -5049,39 +5060,36 @@ export const resolvers = {
|
||||
// Мутации для категорий
|
||||
const categoriesMutations = {
|
||||
// Создать категорию
|
||||
createCategory: async (
|
||||
_: unknown,
|
||||
args: { input: { name: string } }
|
||||
) => {
|
||||
createCategory: async (_: unknown, args: { input: { name: string } }) => {
|
||||
try {
|
||||
// Проверяем есть ли уже категория с таким именем
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { name: args.input.name }
|
||||
where: { name: args.input.name },
|
||||
});
|
||||
|
||||
if (existingCategory) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Категория с таким названием уже существует'
|
||||
message: "Категория с таким названием уже существует",
|
||||
};
|
||||
}
|
||||
|
||||
const category = await prisma.category.create({
|
||||
data: {
|
||||
name: args.input.name
|
||||
}
|
||||
name: args.input.name,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Категория успешно создана',
|
||||
category
|
||||
message: "Категория успешно создана",
|
||||
category,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ошибка создания категории:', error);
|
||||
console.error("Ошибка создания категории:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании категории'
|
||||
message: "Ошибка при создании категории",
|
||||
};
|
||||
}
|
||||
},
|
||||
@ -5089,18 +5097,18 @@ const categoriesMutations = {
|
||||
// Обновить категорию
|
||||
updateCategory: async (
|
||||
_: unknown,
|
||||
args: { id: string, input: { name: string } }
|
||||
args: { id: string; input: { name: string } }
|
||||
) => {
|
||||
try {
|
||||
// Проверяем существует ли категория
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { id: args.id }
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
if (!existingCategory) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Категория не найдена'
|
||||
message: "Категория не найдена",
|
||||
};
|
||||
}
|
||||
|
||||
@ -5108,75 +5116,74 @@ const categoriesMutations = {
|
||||
const duplicateCategory = await prisma.category.findFirst({
|
||||
where: {
|
||||
name: args.input.name,
|
||||
id: { not: args.id }
|
||||
}
|
||||
id: { not: args.id },
|
||||
},
|
||||
});
|
||||
|
||||
if (duplicateCategory) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Категория с таким названием уже существует'
|
||||
message: "Категория с таким названием уже существует",
|
||||
};
|
||||
}
|
||||
|
||||
const category = await prisma.category.update({
|
||||
where: { id: args.id },
|
||||
data: {
|
||||
name: args.input.name
|
||||
}
|
||||
name: args.input.name,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Категория успешно обновлена',
|
||||
category
|
||||
message: "Категория успешно обновлена",
|
||||
category,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ошибка обновления категории:', error);
|
||||
console.error("Ошибка обновления категории:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении категории'
|
||||
message: "Ошибка при обновлении категории",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить категорию
|
||||
deleteCategory: async (
|
||||
_: unknown,
|
||||
args: { id: string }
|
||||
) => {
|
||||
deleteCategory: async (_: unknown, args: { id: string }) => {
|
||||
try {
|
||||
// Проверяем существует ли категория
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { id: args.id }
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
if (!existingCategory) {
|
||||
throw new GraphQLError('Категория не найдена');
|
||||
throw new GraphQLError("Категория не найдена");
|
||||
}
|
||||
|
||||
// Проверяем есть ли товары в этой категории
|
||||
const productsCount = await prisma.product.count({
|
||||
where: { categoryId: args.id }
|
||||
where: { categoryId: args.id },
|
||||
});
|
||||
|
||||
if (productsCount > 0) {
|
||||
throw new GraphQLError('Нельзя удалить категорию, в которой есть товары');
|
||||
throw new GraphQLError(
|
||||
"Нельзя удалить категорию, в которой есть товары"
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.category.delete({
|
||||
where: { id: args.id }
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления категории:', error);
|
||||
console.error("Ошибка удаления категории:", error);
|
||||
if (error instanceof GraphQLError) {
|
||||
throw error;
|
||||
}
|
||||
throw new GraphQLError('Ошибка при удалении категории');
|
||||
throw new GraphQLError("Ошибка при удалении категории");
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Логистические мутации
|
||||
|
@ -181,7 +181,10 @@ export const typeDefs = gql`
|
||||
|
||||
# Заказы поставок расходников
|
||||
createSupplyOrder(input: SupplyOrderInput!): SupplyOrderResponse!
|
||||
updateSupplyOrderStatus(id: ID!, status: SupplyOrderStatus!): SupplyOrderResponse!
|
||||
updateSupplyOrderStatus(
|
||||
id: ID!
|
||||
status: SupplyOrderStatus!
|
||||
): SupplyOrderResponse!
|
||||
|
||||
# Работа с логистикой
|
||||
createLogistics(input: LogisticsInput!): LogisticsResponse!
|
||||
@ -392,6 +395,11 @@ export const typeDefs = gql`
|
||||
OZON
|
||||
}
|
||||
|
||||
enum ProductType {
|
||||
PRODUCT
|
||||
CONSUMABLE
|
||||
}
|
||||
|
||||
enum CounterpartyRequestStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
@ -629,6 +637,7 @@ export const typeDefs = gql`
|
||||
description: String
|
||||
price: Float!
|
||||
quantity: Int!
|
||||
type: ProductType
|
||||
category: Category
|
||||
brand: String
|
||||
color: String
|
||||
@ -650,6 +659,7 @@ export const typeDefs = gql`
|
||||
description: String
|
||||
price: Float!
|
||||
quantity: Int!
|
||||
type: ProductType
|
||||
categoryId: ID
|
||||
brand: String
|
||||
color: String
|
||||
|
Reference in New Issue
Block a user