Compare commits
4 Commits
6b425d075f
...
52107e793e
Author | SHA1 | Date | |
---|---|---|---|
52107e793e | |||
d4a394303d | |||
d3530f37d2 | |||
bfda96c94c |
10
CLAUDE.md
10
CLAUDE.md
@ -62,6 +62,16 @@
|
||||
3. **СЛЕДОВАТЬ WORKFLOW** - не нарушать последовательность статусов
|
||||
4. **ДОКУМЕНТИРОВАТЬ** - обновлять rules-complete.md при решениях проблем
|
||||
|
||||
### ⚡ Принципы качества кода:
|
||||
|
||||
- **Качество кода важнее скорости** - лучше потратить время на правильное решение
|
||||
- **Pre-commit hooks существуют для защиты проекта** - никогда не обходить их
|
||||
- **Исправлять ошибки, а не обходить их** - каждая ошибка ESLint должна быть исправлена
|
||||
- **Обход проверок создает технический долг** - `--no-verify` использовать только в крайних случаях
|
||||
- **Профессиональный подход к конфигурации** - точная настройка инструментов, не "заметание под ковер"
|
||||
|
||||
> 📋 **Подробные правила**: см. разделы 1.2-1.3 в [interaction-integrity-rules.md](./interaction-integrity-rules.md#12--принципы-качества-кода)
|
||||
|
||||
### Правила взаимодействия (кратко):
|
||||
|
||||
- **Двухэтапный процесс**: Планирование → Одобрение → Выполнение
|
||||
|
@ -12,13 +12,23 @@ const compat = new FlatCompat({
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
{
|
||||
// Применяем правила только к папкам с основным кодом
|
||||
files: [
|
||||
"src/**/*.{js,jsx,ts,tsx}",
|
||||
"prisma/**/*.{js,ts}",
|
||||
"scripts/**/*.{js,mjs,ts}"
|
||||
],
|
||||
ignores: [
|
||||
".next/**/*",
|
||||
"node_modules/**/*",
|
||||
"build/**/*",
|
||||
"dist/**/*",
|
||||
"*.config.js",
|
||||
"*.config.mjs"
|
||||
"*.config.mjs",
|
||||
// Игнорируем временные и служебные файлы в корне
|
||||
"diagnostic-script.js",
|
||||
"dev.log",
|
||||
"server.log"
|
||||
],
|
||||
rules: {
|
||||
// TypeScript правила
|
||||
|
@ -21,7 +21,73 @@
|
||||
- ❌ Делать предположения о содержании файлов/компонентов
|
||||
- ❌ Гадать, предполагать, домысливать при неопределенности
|
||||
|
||||
### 1.2 🛑 КОМАНДЫ ЭКСТРЕННОЙ ОСТАНОВКИ
|
||||
### 1.2 ⚡ ПРИНЦИПЫ КАЧЕСТВА КОДА
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Качество кода важнее скорости разработки
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ПРИНЦИПЫ:**
|
||||
|
||||
- ✅ **Качество кода важнее скорости** - лучше потратить время на правильное решение
|
||||
- ✅ **Pre-commit hooks существуют для защиты проекта** - никогда не обходить их
|
||||
- ✅ **Исправлять ошибки, а не обходить их** - каждая ошибка ESLint должна быть исправлена
|
||||
- ✅ **Обход проверок создает технический долг** - `--no-verify` использовать только в крайних случаях
|
||||
- ✅ **Лучше потратить время на исправление, чем накапливать проблемы** - долгосрочная перспектива важнее
|
||||
|
||||
**ПРИ ОШИБКАХ ЛИНТЕРА:**
|
||||
|
||||
1. **Сначала исправить** - разобрать каждую ошибку и исправить правильно
|
||||
2. **Потом коммитить** - только после прохождения всех проверок
|
||||
3. **Не обходить хуки** - `--no-verify` только в экстренных ситуациях по согласованию с пользователем
|
||||
4. **Документировать причины** - если пришлось обойти проверки, записать причину и план исправления
|
||||
|
||||
**ПОРЯДОК ДЕЙСТВИЙ ПРИ БЛОКИРОВКЕ КОММИТА:**
|
||||
|
||||
1. Проанализировать все ошибки ESLint/TypeScript
|
||||
2. Разделить на критические (наши файлы) и предупреждения (старые файлы)
|
||||
3. Исправить критические ошибки в первую очередь
|
||||
4. Обсудить с пользователем стратегию для остальных ошибок
|
||||
5. Только после исправления делать коммит
|
||||
|
||||
### 1.3 🎯 ПРИНЦИПЫ ПРОФЕССИОНАЛЬНОЙ КОНФИГУРАЦИИ
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Профессиональный подход важнее быстрых решений
|
||||
|
||||
**ЗАПРЕЩЕННЫЕ ПРАКТИКИ:**
|
||||
|
||||
- ❌ **Игнорирование по паттернам файлов** - не использовать `.eslintignore` с `*.js`, `check-*.js` и подобным
|
||||
- ❌ **"Заметание под ковер"** - не игнорировать проблемы, а решать их
|
||||
- ❌ **Создание конфигов для несуществующих файлов** - сначала проверить реальность проблемы
|
||||
|
||||
**ПРОФЕССИОНАЛЬНЫЕ ПОДХОДЫ:**
|
||||
|
||||
- ✅ **Точная настройка инструментов** - указывать конкретные файлы/папки в конфигах
|
||||
- ✅ **Организация файловой структуры** - переносить временные файлы в `scripts/`, `tools/`, `debug/`
|
||||
- ✅ **Удаление мусора** - удалять временные/отладочные файлы вместо их игнорирования
|
||||
- ✅ **Принцип "files" вместо "ignores"** - лучше указать что проверять, чем что игнорировать
|
||||
- ✅ **Конкретность конфигурации** - вместо `*.config.js` указать точные файлы
|
||||
|
||||
**АЛГОРИТМ ПРИ ПРОБЛЕМАХ С ЛИНТЕРОМ:**
|
||||
|
||||
1. **Проверить реальность проблемы** - существуют ли проблемные файлы?
|
||||
2. **Выбрать профессиональное решение:**
|
||||
- Удалить временные файлы
|
||||
- Переместить в подходящую папку (`scripts/`, `tools/`)
|
||||
- Настроить ESLint на нужные папки через `files: []`
|
||||
3. **Избегать широких паттернов игнорирования**
|
||||
4. **Документировать решение** если оно неочевидно
|
||||
|
||||
**ПРИМЕРЫ ПРАВИЛЬНЫХ РЕШЕНИЙ:**
|
||||
|
||||
```javascript
|
||||
// ❌ Плохо - широкое игнорирование
|
||||
ignores: ['check-*.js', 'debug-*.js', '*.temp.js']
|
||||
|
||||
// ✅ Хорошо - точная настройка
|
||||
files: ['src/**/*.{js,ts,jsx,tsx}', 'scripts/**/*.{js,ts}']
|
||||
ignores: ['diagnostic-script.js', 'legacy-config.js'] // конкретные файлы
|
||||
```
|
||||
|
||||
### 1.3 🛑 КОМАНДЫ ЭКСТРЕННОЙ ОСТАНОВКИ
|
||||
|
||||
**"СТОП - ЧИТАЙ ПРАВИЛА"** - немедленно останавливает любую работу
|
||||
|
||||
|
17
server.log
Normal file
17
server.log
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
> sferav@0.1.0 dev
|
||||
> next dev --turbopack
|
||||
|
||||
⚠ Port 3000 is in use by process 17170
|
||||
18649
|
||||
23448
|
||||
33312, using available port 3001 instead.
|
||||
▲ Next.js 15.4.1 (Turbopack)
|
||||
- Local: http://localhost:3001
|
||||
- Network: http://192.168.0.101:3001
|
||||
- Environments: .env
|
||||
- Experiments (use with caution):
|
||||
· optimizePackageImports
|
||||
|
||||
✓ Starting...
|
||||
✓ Ready in 897ms
|
@ -51,18 +51,21 @@ interface AuthFlowProps {
|
||||
|
||||
export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
|
||||
const { isAuthenticated, user } = useAuth()
|
||||
|
||||
console.log('🎢 AuthFlow - Полученные props:', { partnerCode, referralCode })
|
||||
console.log('🎢 AuthFlow - Статус авторизации:', { isAuthenticated, hasUser: !!user })
|
||||
|
||||
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('🎢 AuthFlow - Полученные props:', { partnerCode, referralCode })
|
||||
console.warn('🎢 AuthFlow - Статус авторизации:', { isAuthenticated, hasUser: !!user })
|
||||
}
|
||||
|
||||
// Проверяем незавершенную регистрацию: если есть токен, но нет организации - очищаем токен
|
||||
useEffect(() => {
|
||||
// Выполняем только на клиенте после гидрации
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
|
||||
if (isAuthenticated && user && !user.organization) {
|
||||
console.log('🧹 AuthFlow - Обнаружена незавершенная регистрация, очищаем токен')
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('🧹 AuthFlow - Обнаружена незавершенная регистрация, очищаем токен')
|
||||
}
|
||||
// Очищаем токен и данные пользователя
|
||||
localStorage.removeItem('authToken')
|
||||
localStorage.removeItem('userData')
|
||||
@ -71,18 +74,20 @@ export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
|
||||
return
|
||||
}
|
||||
}, [isAuthenticated, user])
|
||||
|
||||
// Начинаем всегда с 'phone' для избежания гидрации,
|
||||
|
||||
// Начинаем всегда с 'phone' для избежания гидрации,
|
||||
// а затем обновляем в useEffect после загрузки клиента
|
||||
const [step, setStep] = useState<AuthStep>('phone')
|
||||
|
||||
|
||||
// Определяем тип регистрации на основе параметров
|
||||
// Только один из них должен быть активен (валидация уже прошла в RegisterPage)
|
||||
const registrationType = partnerCode ? 'PARTNER' : (referralCode ? 'REFERRAL' : null)
|
||||
const registrationType = partnerCode ? 'PARTNER' : referralCode ? 'REFERRAL' : null
|
||||
const activeCode = partnerCode || referralCode || null
|
||||
|
||||
console.log('🎢 AuthFlow - Обработанные данные:', { registrationType, activeCode })
|
||||
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('🎢 AuthFlow - Обработанные данные:', { registrationType, activeCode })
|
||||
}
|
||||
|
||||
const [authData, setAuthData] = useState<AuthData>({
|
||||
phone: '',
|
||||
smsCode: '',
|
||||
@ -98,21 +103,23 @@ export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
|
||||
partnerCode: registrationType === 'PARTNER' ? activeCode : null,
|
||||
referralCode: registrationType === 'REFERRAL' ? activeCode : null,
|
||||
})
|
||||
|
||||
console.log('🎢 AuthFlow - Сохраненные в authData:', {
|
||||
partnerCode: authData.partnerCode,
|
||||
referralCode: authData.referralCode,
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('🎢 AuthFlow - Сохраненные в authData:', {
|
||||
partnerCode: authData.partnerCode,
|
||||
referralCode: authData.referralCode,
|
||||
})
|
||||
}
|
||||
|
||||
// Определяем правильный шаг после гидрации
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return // Только на клиенте
|
||||
|
||||
|
||||
// Если у пользователя есть токен и организация - переходим к завершению
|
||||
if (isAuthenticated && user?.organization) {
|
||||
setStep('complete')
|
||||
}
|
||||
// Если есть токен но нет организации - переходим к выбору кабинета
|
||||
// Если есть токен но нет организации - переходим к выбору кабинета
|
||||
else if (isAuthenticated && !user?.organization) {
|
||||
setStep('cabinet-select')
|
||||
}
|
||||
@ -125,7 +132,9 @@ export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
|
||||
// Обновляем шаг при изменении статуса авторизации
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && step === 'phone') {
|
||||
console.log('🎢 AuthFlow - Пользователь авторизовался, переход к выбору кабинета')
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('🎢 AuthFlow - Пользователь авторизовался, переход к выбору кабинета')
|
||||
}
|
||||
setStep('cabinet-select')
|
||||
}
|
||||
}, [isAuthenticated, step])
|
||||
@ -266,13 +275,8 @@ export function AuthFlow({ partnerCode, referralCode }: AuthFlowProps = {}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
{step === 'phone' && (
|
||||
<PhoneStep
|
||||
onNext={handlePhoneNext}
|
||||
registrationType={registrationType}
|
||||
referrerCode={activeCode}
|
||||
/>
|
||||
<PhoneStep onNext={handlePhoneNext} registrationType={registrationType} referrerCode={activeCode} />
|
||||
)}
|
||||
{step === 'sms' && <SmsStep phone={authData.phone} onNext={handleSmsNext} onBack={handleSmsBack} />}
|
||||
{step === 'cabinet-select' && <CabinetSelectStep onNext={handleCabinetNext} onBack={handleCabinetBack} />}
|
||||
|
@ -67,13 +67,15 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
|
||||
const handleConfirm = async () => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
console.log('📝 ConfirmationStep - Данные для регистрации:', {
|
||||
cabinetType: data.cabinetType,
|
||||
inn: data.inn,
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode,
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('📝 ConfirmationStep - Данные для регистрации:', {
|
||||
cabinetType: data.cabinetType,
|
||||
inn: data.inn,
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
let result
|
||||
@ -82,11 +84,13 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
|
||||
(data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') &&
|
||||
data.inn
|
||||
) {
|
||||
console.log('📝 ConfirmationStep - Вызов registerFulfillmentOrganization с кодами:', {
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode,
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('📝 ConfirmationStep - Вызов registerFulfillmentOrganization с кодами:', {
|
||||
referralCode: data.referralCode,
|
||||
partnerCode: data.partnerCode,
|
||||
})
|
||||
}
|
||||
|
||||
result = await registerFulfillmentOrganization(
|
||||
data.phone.replace(/\D/g, ''),
|
||||
data.inn,
|
||||
|
@ -317,7 +317,9 @@ export function MarketplaceApiStep({ onNext, onBack }: MarketplaceApiStepProps)
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`glass-secondary border-${marketplace.badgeColor}-400/30 text-${marketplace.badgeColor}-300 text-xs`}
|
||||
className={`glass-secondary border-${marketplace.badgeColor}-400/30 text-${
|
||||
marketplace.badgeColor
|
||||
}-300 text-xs`}
|
||||
>
|
||||
{marketplace.badge}
|
||||
</Badge>
|
||||
|
@ -2,20 +2,20 @@
|
||||
|
||||
import { useQuery } from '@apollo/client'
|
||||
import {
|
||||
BarChart3,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
DollarSign,
|
||||
Handshake,
|
||||
Home,
|
||||
LogOut,
|
||||
MessageCircle,
|
||||
Settings,
|
||||
Store,
|
||||
Truck,
|
||||
Users,
|
||||
Warehouse,
|
||||
Wrench,
|
||||
BarChart3,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
DollarSign,
|
||||
Handshake,
|
||||
Home,
|
||||
LogOut,
|
||||
MessageCircle,
|
||||
Settings,
|
||||
Store,
|
||||
Truck,
|
||||
Users,
|
||||
Warehouse,
|
||||
Wrench,
|
||||
} from 'lucide-react'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
|
||||
@ -25,7 +25,6 @@ import { GET_CONVERSATIONS, GET_INCOMING_REQUESTS, GET_PENDING_SUPPLIES_COUNT }
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
|
||||
|
||||
// Компонент для отображения логистических заявок (только для логистики)
|
||||
function LogisticsOrdersNotification() {
|
||||
const { data: pendingData } = useQuery(GET_PENDING_SUPPLIES_COUNT, {
|
||||
@ -112,7 +111,11 @@ export function Sidebar({ isRootInstance = false }: { isRootInstance?: boolean }
|
||||
})
|
||||
|
||||
// Если уже есть корневой сайдбар и это не корневой экземпляр — не рендерим дубликат
|
||||
if (typeof window !== 'undefined' && !isRootInstance && (window as any).__SIDEBAR_ROOT_MOUNTED__) {
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
!isRootInstance &&
|
||||
(window as Window & { __SIDEBAR_ROOT_MOUNTED__?: boolean }).__SIDEBAR_ROOT_MOUNTED__
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -249,7 +252,7 @@ export function Sidebar({ isRootInstance = false }: { isRootInstance?: boolean }
|
||||
|
||||
// Помечаем, что корневой экземпляр смонтирован
|
||||
if (typeof window !== 'undefined' && isRootInstance) {
|
||||
;(window as any).__SIDEBAR_ROOT_MOUNTED__ = true
|
||||
;(window as Window & { __SIDEBAR_ROOT_MOUNTED__?: boolean }).__SIDEBAR_ROOT_MOUNTED__ = true
|
||||
}
|
||||
|
||||
return (
|
||||
@ -677,7 +680,8 @@ export function Sidebar({ isRootInstance = false }: { isRootInstance?: boolean }
|
||||
variant="ghost"
|
||||
className={`w-full ${
|
||||
isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'
|
||||
} text-white/80 hover:bg-red-500/20 hover:text-red-300 cursor-pointer text-xs transition-all duration-200`}
|
||||
} text-white/80 hover:bg-red-500/20 hover:text-red-300 cursor-pointer text-xs
|
||||
transition-all duration-200`}
|
||||
onClick={logout}
|
||||
title={isCollapsed ? 'Выйти' : ''}
|
||||
>
|
||||
|
@ -335,12 +335,12 @@ export function MarketCounterparties() {
|
||||
Прямое деловое сотрудничество с автоматическим добавлением
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 px-3 py-2 glass-input rounded-lg text-white/60 font-mono text-sm truncate">
|
||||
{partnerLinkData?.myPartnerLink || 'http://localhost:3000/register?partner=LOADING'}
|
||||
</div>
|
||||
<Button
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={copyPartnerLink}
|
||||
className="glass-button hover:bg-white/20 transition-all duration-200 px-3"
|
||||
@ -400,10 +400,10 @@ export function MarketCounterparties() {
|
||||
{counterpartiesLoading ? (
|
||||
<span className="inline-block h-6 w-8 bg-white/10 rounded animate-pulse" />
|
||||
) : (
|
||||
counterparties.filter(org => {
|
||||
const monthAgo = new Date();
|
||||
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
||||
return new Date(org.createdAt) > monthAgo;
|
||||
counterparties.filter((org) => {
|
||||
const monthAgo = new Date()
|
||||
monthAgo.setMonth(monthAgo.getMonth() - 1)
|
||||
return new Date(org.createdAt) > monthAgo
|
||||
}).length
|
||||
)}
|
||||
</p>
|
||||
@ -436,13 +436,13 @@ export function MarketCounterparties() {
|
||||
{/* Поиск */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/40" />
|
||||
<GlassInput
|
||||
placeholder="Поиск..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 h-9"
|
||||
/>
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/40" />
|
||||
<GlassInput
|
||||
placeholder="Поиск..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -453,208 +453,213 @@ export function MarketCounterparties() {
|
||||
<Filter className="h-3 w-3 mr-1" />
|
||||
<SelectValue placeholder="Тип" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card border-white/20">
|
||||
<SelectItem value="all">Все</SelectItem>
|
||||
<SelectItem value="FULFILLMENT">Фулфилмент</SelectItem>
|
||||
<SelectItem value="SELLER">Селлер</SelectItem>
|
||||
<SelectItem value="LOGIST">Логистика</SelectItem>
|
||||
<SelectItem value="WHOLESALE">Поставщик</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<SelectContent className="glass-card border-white/20">
|
||||
<SelectItem value="all">Все</SelectItem>
|
||||
<SelectItem value="FULFILLMENT">Фулфилмент</SelectItem>
|
||||
<SelectItem value="SELLER">Селлер</SelectItem>
|
||||
<SelectItem value="LOGIST">Логистика</SelectItem>
|
||||
<SelectItem value="WHOLESALE">Поставщик</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={sortField} onValueChange={(value) => setSortField(value as SortField)}>
|
||||
<SelectTrigger className="glass-input text-white border-white/20 h-9 min-w-[100px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card border-white/20">
|
||||
<SelectItem value="name">Название</SelectItem>
|
||||
<SelectItem value="date">Дата</SelectItem>
|
||||
<SelectItem value="inn">ИНН</SelectItem>
|
||||
<SelectItem value="type">Тип</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={sortField} onValueChange={(value) => setSortField(value as SortField)}>
|
||||
<SelectTrigger className="glass-input text-white border-white/20 h-9 min-w-[100px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card border-white/20">
|
||||
<SelectItem value="name">Название</SelectItem>
|
||||
<SelectItem value="date">Дата</SelectItem>
|
||||
<SelectItem value="inn">ИНН</SelectItem>
|
||||
<SelectItem value="type">Тип</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
|
||||
className="glass-input border-white/20 text-white hover:bg-white/10 h-9 w-9 p-0"
|
||||
>
|
||||
{sortOrder === 'asc' ? <SortAsc className="h-3 w-3" /> : <SortDesc className="h-3 w-3" />}
|
||||
</Button>
|
||||
|
||||
{hasActiveFilters && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearFilters}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10 h-9 w-9 p-0"
|
||||
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
|
||||
className="glass-input border-white/20 text-white hover:bg-white/10 h-9 w-9 p-0"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
{sortOrder === 'asc' ? <SortAsc className="h-3 w-3" /> : <SortDesc className="h-3 w-3" />}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Статистика и быстрые фильтры */}
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<div className="text-white/60">
|
||||
{filteredAndSortedCounterparties.length} из {counterparties.length}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1">
|
||||
{['FULFILLMENT', 'SELLER', 'LOGIST', 'WHOLESALE'].map((type) => {
|
||||
const count = counterparties.filter((org: Organization) => org.type === type).length
|
||||
if (count === 0) return null
|
||||
|
||||
return (
|
||||
{hasActiveFilters && (
|
||||
<Button
|
||||
key={type}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setTypeFilter(typeFilter === type ? 'all' : type)}
|
||||
className={`h-6 px-2 text-xs ${
|
||||
typeFilter === type
|
||||
? getTypeBadgeStyles(type) + ' border'
|
||||
: 'text-white/50 hover:text-white hover:bg-white/10'
|
||||
}`}
|
||||
onClick={clearFilters}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10 h-9 w-9 p-0"
|
||||
>
|
||||
{getTypeLabel(type)} ({count})
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Статистика и быстрые фильтры */}
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<div className="text-white/60">
|
||||
{filteredAndSortedCounterparties.length} из {counterparties.length}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1">
|
||||
{['FULFILLMENT', 'SELLER', 'LOGIST', 'WHOLESALE'].map((type) => {
|
||||
const count = counterparties.filter((org: Organization) => org.type === type).length
|
||||
if (count === 0) return null
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={type}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setTypeFilter(typeFilter === type ? 'all' : type)}
|
||||
className={`h-6 px-2 text-xs ${
|
||||
typeFilter === type
|
||||
? getTypeBadgeStyles(type) + ' border'
|
||||
: 'text-white/50 hover:text-white hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
{getTypeLabel(type)} ({count})
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Таблица контрагентов */}
|
||||
<Card className="glass-card flex-1 overflow-hidden">
|
||||
<div className="h-full overflow-auto">
|
||||
<div className="p-6 space-y-3">
|
||||
{/* Заголовок таблицы */}
|
||||
<div className="p-4 rounded-xl bg-gradient-to-r from-white/5 to-white/10 border border-white/10">
|
||||
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-white/80">
|
||||
<div className="col-span-2 flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4 text-blue-400" />
|
||||
<span>Дата добавления</span>
|
||||
</div>
|
||||
<div className="col-span-3 flex items-center gap-2">
|
||||
<Building className="h-4 w-4 text-green-400" />
|
||||
<span>Организация</span>
|
||||
</div>
|
||||
<div className="col-span-1 text-center flex items-center justify-center">
|
||||
<span>Тип</span>
|
||||
</div>
|
||||
<div className="col-span-3 flex items-center gap-2">
|
||||
<Phone className="h-4 w-4 text-purple-400" />
|
||||
<span>Контакты</span>
|
||||
</div>
|
||||
<div className="col-span-2 flex items-center gap-2">
|
||||
<MapPin className="h-4 w-4 text-orange-400" />
|
||||
<span>Адрес</span>
|
||||
</div>
|
||||
<div className="col-span-1 text-center flex items-center justify-center">
|
||||
<span>Действия</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Строки таблицы */}
|
||||
{counterpartiesLoading ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<div className="text-white/60">Загрузка...</div>
|
||||
</div>
|
||||
) : filteredAndSortedCounterparties.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-64">
|
||||
{counterparties.length === 0 ? (
|
||||
<>
|
||||
<Users className="h-12 w-12 text-white/20 mb-2" />
|
||||
<p className="text-white/60">У вас пока нет контрагентов</p>
|
||||
<p className="text-white/40 text-sm mt-1">Перейдите на другие вкладки, чтобы найти партнеров</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Search className="h-12 w-12 text-white/20 mb-2" />
|
||||
<p className="text-white/60">Ничего не найдено</p>
|
||||
<p className="text-white/40 text-sm mt-1">
|
||||
Попробуйте изменить параметры поиска или фильтрации
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
filteredAndSortedCounterparties.map((organization: Organization) => (
|
||||
<div key={organization.id} className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10">
|
||||
<div className="grid grid-cols-12 gap-4 items-center">
|
||||
<div className="col-span-2 text-white/80">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-3 w-3 text-white/40" />
|
||||
<span className="text-sm">{formatDate(organization.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<OrganizationAvatar organization={organization} size="sm" />
|
||||
<div>
|
||||
<p className="text-white font-medium text-sm">
|
||||
{organization.name || organization.fullName}
|
||||
</p>
|
||||
<p className="text-white/60 text-xs flex items-center gap-1">
|
||||
<Building className="h-3 w-3" />
|
||||
{organization.inn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 text-center">
|
||||
<Badge className={getTypeBadgeStyles(organization.type) + ' text-xs'}>
|
||||
{getTypeLabel(organization.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<div className="space-y-1">
|
||||
{organization.phones && organization.phones.length > 0 && (
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<Phone className="h-3 w-3 mr-2" />
|
||||
<span>{organization.phones[0].value}</span>
|
||||
</div>
|
||||
)}
|
||||
{organization.emails && organization.emails.length > 0 && (
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<Mail className="h-3 w-3 mr-2" />
|
||||
<span className="truncate">{organization.emails[0].value}</span>
|
||||
</div>
|
||||
)}
|
||||
{!organization.phones?.length && !organization.emails?.length && (
|
||||
<span className="text-white/40 text-xs">Нет контактов</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
{organization.address ? (
|
||||
<p className="text-white/60 text-xs line-clamp-2">{organization.address}</p>
|
||||
) : (
|
||||
<span className="text-white/40 text-xs">Не указан</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-1 text-center">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemoveCounterparty(organization.id)}
|
||||
className="hover:bg-red-500/20 text-white/60 hover:text-red-300 h-8 w-8 p-0"
|
||||
title="Удалить из контрагентов"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{/* Таблица контрагентов */}
|
||||
<Card className="glass-card flex-1 overflow-hidden">
|
||||
<div className="h-full overflow-auto">
|
||||
<div className="p-6 space-y-3">
|
||||
{/* Заголовок таблицы */}
|
||||
<div className="p-4 rounded-xl bg-gradient-to-r from-white/5 to-white/10 border border-white/10">
|
||||
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-white/80">
|
||||
<div className="col-span-2 flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4 text-blue-400" />
|
||||
<span>Дата добавления</span>
|
||||
</div>
|
||||
<div className="col-span-3 flex items-center gap-2">
|
||||
<Building className="h-4 w-4 text-green-400" />
|
||||
<span>Организация</span>
|
||||
</div>
|
||||
<div className="col-span-1 text-center flex items-center justify-center">
|
||||
<span>Тип</span>
|
||||
</div>
|
||||
<div className="col-span-3 flex items-center gap-2">
|
||||
<Phone className="h-4 w-4 text-purple-400" />
|
||||
<span>Контакты</span>
|
||||
</div>
|
||||
<div className="col-span-2 flex items-center gap-2">
|
||||
<MapPin className="h-4 w-4 text-orange-400" />
|
||||
<span>Адрес</span>
|
||||
</div>
|
||||
<div className="col-span-1 text-center flex items-center justify-center">
|
||||
<span>Действия</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Строки таблицы */}
|
||||
{counterpartiesLoading ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<div className="text-white/60">Загрузка...</div>
|
||||
</div>
|
||||
) : filteredAndSortedCounterparties.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-64">
|
||||
{counterparties.length === 0 ? (
|
||||
<>
|
||||
<Users className="h-12 w-12 text-white/20 mb-2" />
|
||||
<p className="text-white/60">У вас пока нет контрагентов</p>
|
||||
<p className="text-white/40 text-sm mt-1">
|
||||
Перейдите на другие вкладки, чтобы найти партнеров
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Search className="h-12 w-12 text-white/20 mb-2" />
|
||||
<p className="text-white/60">Ничего не найдено</p>
|
||||
<p className="text-white/40 text-sm mt-1">
|
||||
Попробуйте изменить параметры поиска или фильтрации
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
filteredAndSortedCounterparties.map((organization: Organization) => (
|
||||
<div
|
||||
key={organization.id}
|
||||
className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10"
|
||||
>
|
||||
<div className="grid grid-cols-12 gap-4 items-center">
|
||||
<div className="col-span-2 text-white/80">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-3 w-3 text-white/40" />
|
||||
<span className="text-sm">{formatDate(organization.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<OrganizationAvatar organization={organization} size="sm" />
|
||||
<div>
|
||||
<p className="text-white font-medium text-sm">
|
||||
{organization.name || organization.fullName}
|
||||
</p>
|
||||
<p className="text-white/60 text-xs flex items-center gap-1">
|
||||
<Building className="h-3 w-3" />
|
||||
{organization.inn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 text-center">
|
||||
<Badge className={getTypeBadgeStyles(organization.type) + ' text-xs'}>
|
||||
{getTypeLabel(organization.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<div className="space-y-1">
|
||||
{organization.phones && organization.phones.length > 0 && (
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<Phone className="h-3 w-3 mr-2" />
|
||||
<span>{organization.phones[0].value}</span>
|
||||
</div>
|
||||
)}
|
||||
{organization.emails && organization.emails.length > 0 && (
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<Mail className="h-3 w-3 mr-2" />
|
||||
<span className="truncate">{organization.emails[0].value}</span>
|
||||
</div>
|
||||
)}
|
||||
{!organization.phones?.length && !organization.emails?.length && (
|
||||
<span className="text-white/40 text-xs">Нет контактов</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
{organization.address ? (
|
||||
<p className="text-white/60 text-xs line-clamp-2">{organization.address}</p>
|
||||
) : (
|
||||
<span className="text-white/40 text-xs">Не указан</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-1 text-center">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemoveCounterparty(organization.id)}
|
||||
className="hover:bg-red-500/20 text-white/60 hover:text-red-300 h-8 w-8 p-0"
|
||||
title="Удалить из контрагентов"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
@ -26,7 +26,6 @@ import { GlassInput } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { GET_REFERRAL_DASHBOARD_DATA } from '@/graphql/referral-queries'
|
||||
|
||||
|
||||
export function ReferralsTab() {
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [typeFilter, setTypeFilter] = useState<string>('all')
|
||||
@ -38,10 +37,9 @@ export function ReferralsTab() {
|
||||
errorPolicy: 'all',
|
||||
})
|
||||
|
||||
|
||||
// Отладка для понимания что приходит в data (только в dev режиме)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('🔍 ReferralsTab - полные данные:', {
|
||||
console.warn('🔍 ReferralsTab - полные данные:', {
|
||||
loading,
|
||||
error: error?.message,
|
||||
data,
|
||||
@ -74,7 +72,7 @@ export function ReferralsTab() {
|
||||
// Фильтрация и поиск
|
||||
const filteredReferrals = useMemo(() => {
|
||||
return allReferrals.filter((referral) => {
|
||||
const matchesSearch =
|
||||
const matchesSearch =
|
||||
!searchQuery ||
|
||||
referral.organization.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
referral.organization.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
@ -89,21 +87,31 @@ export function ReferralsTab() {
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case 'SELLER': return 'Селлер'
|
||||
case 'WHOLESALE': return 'Поставщик'
|
||||
case 'FULFILLMENT': return 'Фулфилмент'
|
||||
case 'LOGIST': return 'Логистика'
|
||||
default: return type
|
||||
case 'SELLER':
|
||||
return 'Селлер'
|
||||
case 'WHOLESALE':
|
||||
return 'Поставщик'
|
||||
case 'FULFILLMENT':
|
||||
return 'Фулфилмент'
|
||||
case 'LOGIST':
|
||||
return 'Логистика'
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeBadgeStyles = (type: string) => {
|
||||
switch (type) {
|
||||
case 'SELLER': return 'bg-green-500/20 text-green-300 border-green-500/30'
|
||||
case 'WHOLESALE': return 'bg-purple-500/20 text-purple-300 border-purple-500/30'
|
||||
case 'FULFILLMENT': return 'bg-blue-500/20 text-blue-300 border-blue-500/30'
|
||||
case 'LOGIST': return 'bg-orange-500/20 text-orange-300 border-orange-500/30'
|
||||
default: return 'bg-gray-500/20 text-gray-300 border-gray-500/30'
|
||||
case 'SELLER':
|
||||
return 'bg-green-500/20 text-green-300 border-green-500/30'
|
||||
case 'WHOLESALE':
|
||||
return 'bg-purple-500/20 text-purple-300 border-purple-500/30'
|
||||
case 'FULFILLMENT':
|
||||
return 'bg-blue-500/20 text-blue-300 border-blue-500/30'
|
||||
case 'LOGIST':
|
||||
return 'bg-orange-500/20 text-orange-300 border-orange-500/30'
|
||||
default:
|
||||
return 'bg-gray-500/20 text-gray-300 border-gray-500/30'
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,16 +143,16 @@ export function ReferralsTab() {
|
||||
<h3 className="text-base font-semibold text-white">Реферальная ссылка</h3>
|
||||
</div>
|
||||
<div className="text-xs text-white/60">
|
||||
<span className="text-yellow-400 font-medium">100 сфер</span> за регистрацию +
|
||||
<span className="text-yellow-400 font-medium">100 сфер</span> за регистрацию +
|
||||
<span className="text-yellow-400 font-medium"> 100 сфер</span> за первую сделку
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 px-3 py-2 glass-input rounded-lg text-white/60 font-mono text-sm truncate">
|
||||
{referralLink}
|
||||
</div>
|
||||
<Button
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={copyReferralLink}
|
||||
className="glass-button hover:bg-white/20 transition-all duration-200 px-3"
|
||||
@ -298,15 +306,15 @@ export function ReferralsTab() {
|
||||
<div className="text-white/60">
|
||||
Показано {filteredReferrals.length} из {allReferrals.length} партнеров
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-blue-500/10 text-blue-300">
|
||||
<UserPlus className="h-3 w-3" />
|
||||
<span>Рефералы: {allReferrals.filter(r => r.source === 'REFERRAL_LINK').length}</span>
|
||||
<span>Рефералы: {allReferrals.filter((r) => r.source === 'REFERRAL_LINK').length}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-orange-500/10 text-orange-300">
|
||||
<ShoppingCart className="h-3 w-3" />
|
||||
<span>Бизнес: {allReferrals.filter(r => r.source === 'AUTO_BUSINESS').length}</span>
|
||||
<span>Бизнес: {allReferrals.filter((r) => r.source === 'AUTO_BUSINESS').length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -349,20 +357,26 @@ export function ReferralsTab() {
|
||||
<div className="flex flex-col items-center justify-center h-64">
|
||||
<Gift className="h-12 w-12 text-white/20 mb-2" />
|
||||
<p className="text-white/60">
|
||||
{loading ? 'Загрузка...' : allReferrals.length === 0 ? 'У вас пока нет партнеров' : 'Ничего не найдено'}
|
||||
{loading
|
||||
? 'Загрузка...'
|
||||
: allReferrals.length === 0
|
||||
? 'У вас пока нет партнеров'
|
||||
: 'Ничего не найдено'}
|
||||
</p>
|
||||
<p className="text-white/40 text-sm mt-1">
|
||||
{loading
|
||||
{loading
|
||||
? 'Получаем данные о ваших партнерах...'
|
||||
: allReferrals.length === 0
|
||||
: allReferrals.length === 0
|
||||
? 'Поделитесь реферальной ссылкой или начните работать с клиентами'
|
||||
: 'Попробуйте изменить параметры поиска'
|
||||
}
|
||||
: 'Попробуйте изменить параметры поиска'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredReferrals.map((referral) => (
|
||||
<div key={referral.id} className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10">
|
||||
<div
|
||||
key={referral.id}
|
||||
className="p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-200 border border-white/10"
|
||||
>
|
||||
<div className="grid grid-cols-12 gap-4 items-center">
|
||||
<div className="col-span-2 text-white/80">
|
||||
<div className="flex items-center gap-2">
|
||||
@ -422,4 +436,4 @@ export function ReferralsTab() {
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,16 @@ const mergedResolvers = mergeResolvers(
|
||||
// Временно добавляем старые резолверы ПЕРВЫМИ, чтобы новые их перезаписали
|
||||
{
|
||||
Query: (() => {
|
||||
const { myEmployees: _myEmployees, logisticsPartners: _logisticsPartners, pendingSuppliesCount: _pendingSuppliesCount, myReferralLink: _myReferralLink, myPartnerLink: _myPartnerLink, myReferralStats: _myReferralStats, myReferrals: _myReferrals, ...filteredQuery } = oldResolvers.Query || {}
|
||||
const {
|
||||
myEmployees: _myEmployees,
|
||||
logisticsPartners: _logisticsPartners,
|
||||
pendingSuppliesCount: _pendingSuppliesCount,
|
||||
myReferralLink: _myReferralLink,
|
||||
myPartnerLink: _myPartnerLink,
|
||||
myReferralStats: _myReferralStats,
|
||||
myReferrals: _myReferrals,
|
||||
...filteredQuery
|
||||
} = oldResolvers.Query || {}
|
||||
return filteredQuery
|
||||
})(),
|
||||
Mutation: {
|
||||
@ -92,5 +101,4 @@ const mergedResolvers = mergeResolvers(
|
||||
referralResolvers,
|
||||
)
|
||||
|
||||
|
||||
export const resolvers = mergedResolvers
|
||||
|
@ -32,7 +32,7 @@ export const referralResolvers = {
|
||||
}
|
||||
|
||||
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?ref=${organization.referralCode}`
|
||||
|
||||
|
||||
return link || 'http://localhost:3000/register?ref=ERROR'
|
||||
},
|
||||
|
||||
@ -54,12 +54,12 @@ export const referralResolvers = {
|
||||
}
|
||||
|
||||
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?partner=${organization.referralCode}`
|
||||
|
||||
|
||||
return link
|
||||
},
|
||||
|
||||
// Получить статистику по рефералам
|
||||
myReferralStats: async (_: unknown, __: unknown, context: Context) => {
|
||||
myReferralStats: async (_: unknown, __: unknown, _context: Context) => {
|
||||
// Простая заглушка для устранения ошибки 500
|
||||
return {
|
||||
totalPartners: 0,
|
||||
@ -104,4 +104,4 @@ export const referralResolvers = {
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user