Обновления системы после анализа и оптимизации архитектуры

- Обновлена схема Prisma с новыми полями и связями
- Актуализированы правила системы в rules-complete.md
- Оптимизированы GraphQL типы, запросы и мутации
- Улучшены компоненты интерфейса и валидация данных
- Исправлены критические ESLint ошибки: удалены неиспользуемые импорты и переменные
- Добавлены тестовые файлы для проверки функционала

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-06 23:44:49 +03:00
parent c2b342a527
commit 10af6f08cc
33 changed files with 3259 additions and 1319 deletions

View File

@ -31,6 +31,7 @@ import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { UPDATE_USER_PROFILE, UPDATE_ORGANIZATION_BY_INN } from '@/graphql/mutations'
import { GET_ME } from '@/graphql/queries'
@ -87,6 +88,9 @@ export function UserSettings() {
// API ключи маркетплейсов
wildberriesApiKey: '',
ozonApiKey: '',
// Рынок для поставщиков
market: '',
})
// Загружаем данные организации при монтировании компонента
@ -130,10 +134,13 @@ export function UserSettings() {
} = {}
try {
if (org.managementPost && typeof org.managementPost === 'string') {
customContacts = JSON.parse(org.managementPost)
// Проверяем, что строка начинается с { или [, иначе это не JSON
if (org.managementPost.trim().startsWith('{') || org.managementPost.trim().startsWith('[')) {
customContacts = JSON.parse(org.managementPost)
}
}
} catch (e) {
console.warn('Ошибка парсинга managementPost:', e)
} catch {
// Игнорируем ошибки парсинга
}
setFormData({
@ -154,6 +161,7 @@ export function UserSettings() {
corrAccount: customContacts?.bankDetails?.corrAccount || '',
wildberriesApiKey: '',
ozonApiKey: '',
market: org.market || 'none',
})
}
}, [user])
@ -290,7 +298,6 @@ export function UserSettings() {
})
// TODO: Сохранить партнерский код в базе данных
console.warn('Partner code generated:', partnerCode)
} catch (error) {
console.error('Error generating partner link:', error)
setSaveMessage({ type: 'error', text: 'Ошибка при генерации ссылки' })
@ -342,7 +349,7 @@ export function UserSettings() {
avatar: avatarUrl,
},
},
update: (cache, { data }) => {
update: (cache, { data }: { data?: any }) => {
if (data?.updateUserProfile?.success) {
// Обновляем кеш Apollo Client
try {
@ -358,8 +365,8 @@ export function UserSettings() {
},
})
}
} catch (error) {
console.warn('Cache update error:', error)
} catch {
// Игнорируем ошибки обновления кеша
}
}
},
@ -518,6 +525,7 @@ export function UserSettings() {
const handleInputChange = (field: string, value: string) => {
let processedValue = value
// Применяем маски и валидации
switch (field) {
case 'orgPhone':
@ -582,6 +590,59 @@ export function UserSettings() {
}
}
// Проверка наличия изменений в форме
const hasFormChanges = () => {
if (!user?.organization) return false
const org = user.organization
// Извлекаем текущий телефон из organization.phones
let currentOrgPhone = '+7'
if (org.phones && Array.isArray(org.phones) && org.phones.length > 0) {
currentOrgPhone = org.phones[0].value || org.phones[0] || '+7'
}
// Извлекаем текущий email из organization.emails
let currentEmail = ''
if (org.emails && Array.isArray(org.emails) && org.emails.length > 0) {
currentEmail = org.emails[0].value || org.emails[0] || ''
}
// Извлекаем дополнительные данные из managementPost
let customContacts: any = {}
try {
if (org.managementPost && typeof org.managementPost === 'string') {
// Проверяем, что строка начинается с { или [, иначе это не JSON
if (org.managementPost.trim().startsWith('{') || org.managementPost.trim().startsWith('[')) {
customContacts = JSON.parse(org.managementPost)
}
}
} catch {
// ignore parse errors
}
// Нормализуем значения для сравнения
const normalizeValue = (value: string | null | undefined) => value || ''
const normalizeMarketValue = (value: string | null | undefined) => value || 'none'
// Проверяем изменения в полях
const changes = [
normalizeValue(formData.orgPhone) !== normalizeValue(currentOrgPhone),
normalizeValue(formData.managerName) !== normalizeValue(user?.managerName),
normalizeValue(formData.telegram) !== normalizeValue(customContacts?.telegram),
normalizeValue(formData.whatsapp) !== normalizeValue(customContacts?.whatsapp),
normalizeValue(formData.email) !== normalizeValue(currentEmail),
normalizeMarketValue(formData.market) !== normalizeMarketValue(org.market),
normalizeValue(formData.bankName) !== normalizeValue(customContacts?.bankDetails?.bankName),
normalizeValue(formData.bik) !== normalizeValue(customContacts?.bankDetails?.bik),
normalizeValue(formData.accountNumber) !== normalizeValue(customContacts?.bankDetails?.accountNumber),
normalizeValue(formData.corrAccount) !== normalizeValue(customContacts?.bankDetails?.corrAccount),
]
const hasChanges = changes.some(changed => changed)
return hasChanges
}
// Проверка наличия ошибок валидации
const hasValidationErrors = () => {
const fields = [
@ -658,6 +719,7 @@ export function UserSettings() {
bik?: string
accountNumber?: string
corrAccount?: string
market?: string
} = {}
// orgName больше не редактируется - устанавливается только при регистрации
@ -670,6 +732,7 @@ export function UserSettings() {
if (formData.bik?.trim()) inputData.bik = formData.bik.trim()
if (formData.accountNumber?.trim()) inputData.accountNumber = formData.accountNumber.trim()
if (formData.corrAccount?.trim()) inputData.corrAccount = formData.corrAccount.trim()
if (formData.market) inputData.market = formData.market
const result = await updateUserProfile({
variables: {
@ -715,7 +778,6 @@ export function UserSettings() {
}
if (isNaN(date.getTime())) {
console.warn('Invalid date string:', dateString)
return 'Неверная дата'
}
@ -724,8 +786,7 @@ export function UserSettings() {
month: 'long',
day: 'numeric',
})
} catch (error) {
console.error('Error formatting date:', error, dateString)
} catch {
return 'Ошибка даты'
}
}
@ -833,9 +894,9 @@ export function UserSettings() {
<Button
size="sm"
onClick={handleSave}
disabled={hasValidationErrors() || isSaving}
disabled={hasValidationErrors() || isSaving || !hasFormChanges()}
className={`glass-button text-white cursor-pointer ${
hasValidationErrors() || isSaving ? 'opacity-50 cursor-not-allowed' : ''
hasValidationErrors() || isSaving || !hasFormChanges() ? 'opacity-50 cursor-not-allowed' : ''
}`}
>
<Save className="h-4 w-4 mr-2" />
@ -1070,9 +1131,9 @@ export function UserSettings() {
<Button
size="sm"
onClick={handleSave}
disabled={hasValidationErrors() || isSaving}
disabled={hasValidationErrors() || isSaving || !hasFormChanges()}
className={`glass-button text-white cursor-pointer ${
hasValidationErrors() || isSaving ? 'opacity-50 cursor-not-allowed' : ''
hasValidationErrors() || isSaving || !hasFormChanges() ? 'opacity-50 cursor-not-allowed' : ''
}`}
>
<Save className="h-4 w-4 mr-2" />
@ -1256,6 +1317,41 @@ export function UserSettings() {
</div>
</div>
)}
{/* Настройка рынка для поставщиков */}
{user?.organization?.type === 'WHOLESALE' && (
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-white/80 text-sm mb-2 flex items-center gap-2">
🏪 Физический рынок
</Label>
{isEditing ? (
<Select value={formData.market || 'none'} onValueChange={(value) => handleInputChange('market', value)}>
<SelectTrigger className="glass-input text-white h-10 text-sm">
<SelectValue placeholder="Выберите рынок" />
</SelectTrigger>
<SelectContent className="glass-card">
<SelectItem value="none">Не указан</SelectItem>
<SelectItem value="sadovod" className="text-white">Садовод</SelectItem>
<SelectItem value="tyak-moscow" className="text-white">ТЯК Москва</SelectItem>
</SelectContent>
</Select>
) : (
<Input
value={formData.market && formData.market !== 'none' ?
(formData.market === 'sadovod' ? 'Садовод' :
formData.market === 'tyak-moscow' ? 'ТЯК Москва' :
formData.market) : 'Не указан'}
readOnly
className="glass-input text-white h-10 read-only:opacity-70"
/>
)}
<p className="text-white/50 text-xs mt-1">
Физический рынок, где работает поставщик. Товары наследуют рынок от организации.
</p>
</div>
</div>
)}
</div>
</Card>
</TabsContent>
@ -1297,7 +1393,7 @@ export function UserSettings() {
<Button
size="sm"
onClick={handleSave}
disabled={hasValidationErrors() || isSaving}
disabled={hasValidationErrors() || isSaving || !hasFormChanges()}
className={`glass-button text-white cursor-pointer ${
hasValidationErrors() || isSaving ? 'opacity-50 cursor-not-allowed' : ''
}`}
@ -1404,7 +1500,7 @@ export function UserSettings() {
<Button
size="sm"
onClick={handleSave}
disabled={hasValidationErrors() || isSaving}
disabled={hasValidationErrors() || isSaving || !hasFormChanges()}
className={`glass-button text-white cursor-pointer ${
hasValidationErrors() || isSaving ? 'opacity-50 cursor-not-allowed' : ''
}`}