
- Добавлены колонки Объём и Грузовые места между Цена товаров и Статус - Реализованы инпуты для ввода volume и packagesCount в статусе PENDING для роли WHOLESALE - Добавлена мутация UPDATE_SUPPLY_PARAMETERS с проверками безопасности - Скрыта строка Поставщик для роли WHOLESALE (поставщик знает свои данные) - Исправлено выравнивание таблицы при скрытии уровня поставщика - Реорганизованы документы: legacy-rules/, docs/, docs-and-reports/ ВНИМАНИЕ: Компонент multilevel-supplies-table.tsx (1697 строк) нарушает правило модульной архитектуры (>800 строк требует рефакторинга) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1088 lines
34 KiB
Markdown
1088 lines
34 KiB
Markdown
# UI КОМПОНЕНТЫ СИСТЕМЫ SFERA
|
||
|
||
## 🎯 ОБЗОР UI СИСТЕМЫ
|
||
|
||
SFERA использует современную дизайн-систему основанную на **Radix UI**, **Class Variance Authority (CVA)** и **Tailwind CSS** с уникальным **Glass Morphism** стилем. Система включает 36 специализированных UI компонентов с полной типизацией TypeScript.
|
||
|
||
### Архитектурные принципы:
|
||
|
||
- **Headless UI** - Radix UI для функциональности + кастомная стилизация
|
||
- **Variant-driven** - CVA для типизированных вариантов компонентов
|
||
- **Glass Morphism** - Современные полупрозрачные эффекты с backdrop-filter
|
||
- **Accessibility First** - Полная поддержка ARIA и клавиатурной навигации
|
||
- **TypeScript Native** - Строгая типизация всех props и вариантов
|
||
|
||
## 📦 ПОЛНЫЙ КАТАЛОГ КОМПОНЕНТОВ (36 компонентов)
|
||
|
||
### 🔘 1. BUTTON (button.tsx)
|
||
|
||
**Описание:** Основной интерактивный элемент с множественными вариантами дизайна.
|
||
|
||
```typescript
|
||
interface ButtonProps {
|
||
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'glass' | 'glass-secondary'
|
||
size?: 'default' | 'sm' | 'lg' | 'icon'
|
||
asChild?: boolean
|
||
}
|
||
```
|
||
|
||
**Варианты стилей:**
|
||
|
||
- **`default`** - основная фиолетовая кнопка `bg-primary text-primary-foreground`
|
||
- **`destructive`** - красная кнопка для опасных действий `bg-destructive text-white`
|
||
- **`outline`** - кнопка с границей `border bg-background`
|
||
- **`secondary`** - вторичная кнопка `bg-secondary text-secondary-foreground`
|
||
- **`ghost`** - прозрачная кнопка `hover:bg-accent`
|
||
- **`link`** - текстовая ссылка `text-primary underline-offset-4`
|
||
- **`glass`** - Glass Morphism стиль с градиентом
|
||
- **`glass-secondary`** - полупрозрачная Glass кнопка
|
||
|
||
**Размеры:**
|
||
|
||
- **`default`** - `h-9 px-4 py-2` (36px высота)
|
||
- **`sm`** - `h-8 px-3` (32px высота)
|
||
- **`lg`** - `h-10 px-6` (40px высота)
|
||
- **`icon`** - `size-9` (36x36px квадрат)
|
||
|
||
**Пример использования:**
|
||
|
||
```typescript
|
||
<Button variant="glass" size="lg">
|
||
Сохранить изменения
|
||
</Button>
|
||
```
|
||
|
||
### 🃏 2. CARD (card.tsx)
|
||
|
||
**Описание:** Контейнер для группировки связанного контента с составной архитектурой.
|
||
|
||
```typescript
|
||
// Составные компоненты
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Заголовок карточки</CardTitle>
|
||
<CardDescription>Описание содержимого</CardDescription>
|
||
<CardAction>Действие</CardAction>
|
||
</CardHeader>
|
||
<CardContent>
|
||
Основное содержимое
|
||
</CardContent>
|
||
<CardFooter>
|
||
Нижняя часть
|
||
</CardFooter>
|
||
</Card>
|
||
```
|
||
|
||
**CSS классы:**
|
||
|
||
- **Card**: `bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm`
|
||
- **CardHeader**: Использует CSS Grid для автоматического позиционирования
|
||
- **CardTitle**: `leading-none font-semibold`
|
||
- **CardDescription**: `text-muted-foreground text-sm`
|
||
|
||
### ⌨️ 3. INPUT (input.tsx)
|
||
|
||
**Описание:** Поле ввода текста с поддержкой Glass Morphism и состояний фокуса.
|
||
|
||
```typescript
|
||
interface InputProps extends React.ComponentProps<'input'> {
|
||
// Стандартные HTML input props
|
||
}
|
||
|
||
// Два варианта стилизации
|
||
<Input placeholder="Стандартное поле" />
|
||
<GlassInput placeholder="Glass Morphism поле" />
|
||
```
|
||
|
||
**Стили Input:**
|
||
|
||
- Базовый класс: `h-9 w-full rounded-md border bg-transparent px-3 py-1`
|
||
- Фокус: `focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]`
|
||
- Ошибка: `aria-invalid:ring-destructive/20 aria-invalid:border-destructive`
|
||
|
||
**Стили GlassInput:**
|
||
|
||
- Базовый класс: `glass-input text-white placeholder:text-white/60`
|
||
- Размеры: `h-11 rounded-lg px-4 py-3` (больше обычного input)
|
||
- Эффекты: полупрозрачный фон с backdrop-filter
|
||
|
||
### 🏷️ 4. BADGE (badge.tsx)
|
||
|
||
**Описание:** Небольшие метки для отображения статуса, категорий или счетчиков.
|
||
|
||
```typescript
|
||
interface BadgeProps {
|
||
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
|
||
asChild?: boolean
|
||
}
|
||
```
|
||
|
||
**Варианты:**
|
||
|
||
- **`default`** - `bg-primary text-primary-foreground`
|
||
- **`secondary`** - `bg-secondary text-secondary-foreground`
|
||
- **`destructive`** - `bg-destructive text-white`
|
||
- **`outline`** - `text-foreground border` (прозрачный фон)
|
||
|
||
**Базовые стили:**
|
||
|
||
- Размер: `px-2 py-0.5 text-xs font-medium`
|
||
- Форма: `rounded-md border`
|
||
- Поддержка иконок: `[&>svg]:size-3 gap-1`
|
||
|
||
### 📊 5. PROGRESS (progress.tsx)
|
||
|
||
**Описание:** Индикатор прогресса для отображения выполнения задач.
|
||
|
||
```typescript
|
||
<Progress value={75} className="w-full" />
|
||
```
|
||
|
||
### 📱 6. ALERT (alert.tsx)
|
||
|
||
**Описание:** Компонент для отображения важных сообщений пользователю.
|
||
|
||
```typescript
|
||
<Alert>
|
||
<AlertTitle>Внимание</AlertTitle>
|
||
<AlertDescription>Важное сообщение для пользователя</AlertDescription>
|
||
</Alert>
|
||
```
|
||
|
||
### 🗂️ 7. TABS (tabs.tsx)
|
||
|
||
**Описание:** Система вкладок для переключения между разными представлениями.
|
||
|
||
```typescript
|
||
<Tabs defaultValue="tab1">
|
||
<TabsList>
|
||
<TabsTrigger value="tab1">Вкладка 1</TabsTrigger>
|
||
<TabsTrigger value="tab2">Вкладка 2</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="tab1">Содержимое 1</TabsContent>
|
||
<TabsContent value="tab2">Содержимое 2</TabsContent>
|
||
</Tabs>
|
||
```
|
||
|
||
**Особенности стилизации:**
|
||
|
||
- Список вкладок: Glass Morphism фон `background: rgba(255, 255, 255, 0.12)`
|
||
- Активная вкладка: `background: rgba(255, 255, 255, 0.2)` с белым текстом
|
||
- Hover эффект: `background: rgba(255, 255, 255, 0.1)`
|
||
|
||
### 📝 8. TEXTAREA (textarea.tsx)
|
||
|
||
**Описание:** Многострочное поле ввода текста.
|
||
|
||
```typescript
|
||
<Textarea placeholder="Введите текст..." rows={4} />
|
||
```
|
||
|
||
### ☑️ 9. CHECKBOX (checkbox.tsx)
|
||
|
||
**Описание:** Чекбокс для выбора опций.
|
||
|
||
```typescript
|
||
<Checkbox checked={isChecked} onCheckedChange={setIsChecked} />
|
||
```
|
||
|
||
### 🎚️ 10. SWITCH (switch.tsx)
|
||
|
||
**Описание:** Переключатель для включения/выключения функций.
|
||
|
||
```typescript
|
||
<Switch checked={isEnabled} onCheckedChange={setIsEnabled} />
|
||
```
|
||
|
||
### 🎚️ 11. SLIDER (slider.tsx)
|
||
|
||
**Описание:** Ползунок для выбора числовых значений.
|
||
|
||
```typescript
|
||
<Slider defaultValue={[50]} max={100} step={1} />
|
||
```
|
||
|
||
### 📅 12. CALENDAR (calendar.tsx)
|
||
|
||
**Описание:** Компонент календаря для выбора дат.
|
||
|
||
```typescript
|
||
<Calendar mode="single" selected={date} onSelect={setDate} />
|
||
```
|
||
|
||
### 📅 13. DATE-PICKER (date-picker.tsx)
|
||
|
||
**Описание:** Поле выбора даты с календарем.
|
||
|
||
```typescript
|
||
<DatePicker value={date} onChange={setDate} />
|
||
```
|
||
|
||
### 📅 14. GLASS-DATE-PICKER (glass-date-picker.tsx)
|
||
|
||
**Описание:** Date picker в Glass Morphism стиле.
|
||
|
||
```typescript
|
||
<GlassDatePicker value={date} onChange={setDate} />
|
||
```
|
||
|
||
### 📋 15. SELECT (select.tsx)
|
||
|
||
**Описание:** Выпадающий список для выбора опций.
|
||
|
||
```typescript
|
||
<Select value={value} onValueChange={setValue}>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="Выберите опцию" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="option1">Опция 1</SelectItem>
|
||
<SelectItem value="option2">Опция 2</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
```
|
||
|
||
### 📋 16. GLASS-SELECT (glass-select.tsx)
|
||
|
||
**Описание:** Select в Glass Morphism стиле для темных фонов.
|
||
|
||
### 🏷️ 17. LABEL (label.tsx)
|
||
|
||
**Описание:** Метки для полей форм с accessibility.
|
||
|
||
```typescript
|
||
<Label htmlFor="email">Email адрес</Label>
|
||
<Input id="email" type="email" />
|
||
```
|
||
|
||
### 👤 18. AVATAR (avatar.tsx)
|
||
|
||
**Описание:** Отображение аватаров пользователей с fallback.
|
||
|
||
```typescript
|
||
<Avatar>
|
||
<AvatarImage src="/avatar.jpg" alt="User" />
|
||
<AvatarFallback>JD</AvatarFallback>
|
||
</Avatar>
|
||
```
|
||
|
||
### 🌐 19. DIALOG (dialog.tsx)
|
||
|
||
**Описание:** Модальные окна для важного контента.
|
||
|
||
```typescript
|
||
<Dialog>
|
||
<DialogTrigger>Открыть диалог</DialogTrigger>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Заголовок</DialogTitle>
|
||
<DialogDescription>Описание</DialogDescription>
|
||
</DialogHeader>
|
||
<DialogFooter>
|
||
<Button>Сохранить</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
```
|
||
|
||
### ⚠️ 20. ALERT-DIALOG (alert-dialog.tsx)
|
||
|
||
**Описание:** Критичные диалоги подтверждения.
|
||
|
||
```typescript
|
||
<AlertDialog>
|
||
<AlertDialogTrigger>Удалить</AlertDialogTrigger>
|
||
<AlertDialogContent>
|
||
<AlertDialogHeader>
|
||
<AlertDialogTitle>Подтвердите удаление</AlertDialogTitle>
|
||
<AlertDialogDescription>
|
||
Это действие нельзя отменить.
|
||
</AlertDialogDescription>
|
||
</AlertDialogHeader>
|
||
<AlertDialogFooter>
|
||
<AlertDialogCancel>Отмена</AlertDialogCancel>
|
||
<AlertDialogAction>Удалить</AlertDialogAction>
|
||
</AlertDialogFooter>
|
||
</AlertDialogContent>
|
||
</AlertDialog>
|
||
```
|
||
|
||
### 💬 21. POPOVER (popover.tsx)
|
||
|
||
**Описание:** Всплывающие элементы для дополнительного контента.
|
||
|
||
```typescript
|
||
<Popover>
|
||
<PopoverTrigger>Показать информацию</PopoverTrigger>
|
||
<PopoverContent>
|
||
Дополнительная информация
|
||
</PopoverContent>
|
||
</Popover>
|
||
```
|
||
|
||
### 📱 22. DROPDOWN-MENU (dropdown-menu.tsx)
|
||
|
||
**Описание:** Выпадающие меню для действий и навигации.
|
||
|
||
```typescript
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger>Меню</DropdownMenuTrigger>
|
||
<DropdownMenuContent>
|
||
<DropdownMenuItem>Действие 1</DropdownMenuItem>
|
||
<DropdownMenuSeparator />
|
||
<DropdownMenuItem>Действие 2</DropdownMenuItem>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
```
|
||
|
||
### ➖ 23. SEPARATOR (separator.tsx)
|
||
|
||
**Описание:** Визуальные разделители контента.
|
||
|
||
```typescript
|
||
<Separator orientation="horizontal" />
|
||
<Separator orientation="vertical" />
|
||
```
|
||
|
||
### 📱 24. PHONE-INPUT (phone-input.tsx)
|
||
|
||
**Описание:** Специализированное поле для ввода номеров телефонов.
|
||
|
||
```typescript
|
||
<PhoneInput value={phone} onChange={setPhone} />
|
||
```
|
||
|
||
### 💀 25. SKELETON (skeleton.tsx)
|
||
|
||
**Описание:** Плейсхолдеры для загружающегося контента.
|
||
|
||
```typescript
|
||
<Skeleton className="h-4 w-full" />
|
||
<Skeleton className="h-8 w-8 rounded-full" />
|
||
```
|
||
|
||
### 🛒 26. PRODUCT-CARD-SKELETON (product-card-skeleton.tsx)
|
||
|
||
**Описание:** Специализированный скелетон для карточек товаров.
|
||
|
||
```typescript
|
||
<ProductCardSkeleton />
|
||
```
|
||
|
||
### ⏳ 27. LOADING-FALLBACK (loading-fallback.tsx)
|
||
|
||
**Описание:** Компонент загрузки для асинхронного контента.
|
||
|
||
```typescript
|
||
<LoadingFallback text="Загрузка данных..." />
|
||
```
|
||
|
||
## 🎵 МЕДИА КОМПОНЕНТЫ
|
||
|
||
### 🎤 28. VOICE-RECORDER (voice-recorder.tsx)
|
||
|
||
**Описание:** Запись голосовых сообщений с реального времени UI.
|
||
|
||
```typescript
|
||
<VoiceRecorder onRecordingComplete={handleRecording} />
|
||
```
|
||
|
||
### ▶️ 29. VOICE-PLAYER (voice-player.tsx)
|
||
|
||
**Описание:** Воспроизведение аудио сообщений с прогресс-баром.
|
||
|
||
```typescript
|
||
<VoicePlayer audioUrl="/audio.mp3" duration={30} />
|
||
```
|
||
|
||
### 🖼️ 30. IMAGE-MESSAGE (image-message.tsx)
|
||
|
||
**Описание:** Отображение изображений в сообщениях.
|
||
|
||
```typescript
|
||
<ImageMessage src="/image.jpg" alt="Сообщение" />
|
||
```
|
||
|
||
### 🔍 31. IMAGE-LIGHTBOX (image-lightbox.tsx)
|
||
|
||
**Описание:** Полноэкранный просмотр изображений.
|
||
|
||
```typescript
|
||
<ImageLightbox images={imageUrls} initialIndex={0} />
|
||
```
|
||
|
||
### 📄 32. FILE-MESSAGE (file-message.tsx)
|
||
|
||
**Описание:** Отображение файловых вложений.
|
||
|
||
```typescript
|
||
<FileMessage fileName="document.pdf" fileSize={1024000} fileUrl="/file.pdf" />
|
||
```
|
||
|
||
### 📤 33. FILE-UPLOADER (file-uploader.tsx)
|
||
|
||
**Описание:** Загрузка файлов с drag & drop.
|
||
|
||
```typescript
|
||
<FileUploader onFileSelect={handleFiles} accept=".pdf,.doc,.docx" />
|
||
```
|
||
|
||
### 😀 34. EMOJI-PICKER (emoji-picker.tsx)
|
||
|
||
**Описание:** Выбор эмодзи для сообщений.
|
||
|
||
```typescript
|
||
<EmojiPicker onEmojiSelect={handleEmojiSelect} />
|
||
```
|
||
|
||
### 📊 35. CHART (chart.tsx)
|
||
|
||
**Описание:** Компоненты для отображения графиков и диаграмм.
|
||
|
||
```typescript
|
||
<Chart data={chartData} type="line" />
|
||
```
|
||
|
||
### 🔔 36. SONNER (sonner.tsx)
|
||
|
||
**Описание:** Система toast уведомлений.
|
||
|
||
```typescript
|
||
import { toast } from 'sonner'
|
||
|
||
toast.success('Операция выполнена успешно')
|
||
toast.error('Произошла ошибка')
|
||
toast.info('Информационное сообщение')
|
||
```
|
||
|
||
## 📊 КАСТОМНЫЕ ТАБЛИЦЫ СИСТЕМЫ
|
||
|
||
### 🏷️ 37. MULTILEVEL SUPPLIES TABLE (multilevel-supplies-table.tsx)
|
||
|
||
**Описание:** Многоуровневая таблица поставок для кабинета селлера в разделе "Мои поставки".
|
||
|
||
**Интерфейсы:**
|
||
|
||
```typescript
|
||
interface MultiLevelSuppliesTableProps {
|
||
supplies?: SupplyOrderFromGraphQL[]
|
||
loading?: boolean
|
||
userRole?: 'SELLER' | 'WHOLESALE' | 'FULFILLMENT' | 'LOGIST'
|
||
onSupplyAction?: (supplyId: string, action: string) => void
|
||
}
|
||
|
||
interface SupplyOrderFromGraphQL {
|
||
id: string
|
||
organizationId: string
|
||
partnerId: string
|
||
partner: {
|
||
id: string
|
||
name?: string
|
||
fullName?: string
|
||
inn: string
|
||
address?: string
|
||
type: string
|
||
}
|
||
deliveryDate: string
|
||
status: string
|
||
totalAmount: number
|
||
totalItems: number
|
||
fulfillmentCenter?: {
|
||
id: string
|
||
name?: string
|
||
address?: string
|
||
}
|
||
routes: Route[]
|
||
items: SupplyItem[]
|
||
createdAt: string
|
||
}
|
||
```
|
||
|
||
**Особенности:**
|
||
|
||
- Трехуровневая структура: Поставка → Маршруты → Товары
|
||
- Раскрываемые/сворачиваемые уровни
|
||
- Различные представления для разных ролей пользователей
|
||
- Glass morphism дизайн с полупрозрачными карточками
|
||
|
||
**Использование:**
|
||
|
||
```typescript
|
||
<MultiLevelSuppliesTable
|
||
supplies={suppliesData}
|
||
loading={isLoading}
|
||
userRole="SELLER"
|
||
onSupplyAction={(id, action) => handleSupplyAction(id, action)}
|
||
/>
|
||
```
|
||
|
||
### 📦 38. GOODS SUPPLIES TABLE (goods-supplies-table.tsx)
|
||
|
||
**Описание:** Таблица товарных поставок с детальной структурой.
|
||
|
||
**Интерфейсы:**
|
||
|
||
```typescript
|
||
interface GoodsSuppliesTableProps {
|
||
supplies?: GoodsSupply[]
|
||
loading?: boolean
|
||
onActionClick?: (supplyId: string, action: string) => void
|
||
}
|
||
|
||
interface GoodsSupply {
|
||
id: string
|
||
number: string
|
||
creationMethod: 'cards' | 'suppliers' // 📱 карточки / 🏢 поставщик
|
||
date: string
|
||
status: SupplyStatus
|
||
totalAmount: number
|
||
routes: GoodsSupplyRoute[]
|
||
}
|
||
|
||
interface GoodsSupplyRoute {
|
||
id: string
|
||
from: string
|
||
fromAddress: string
|
||
to: string
|
||
toAddress: string
|
||
wholesalers: GoodsSupplyWholesaler[]
|
||
totalProductPrice: number
|
||
fulfillmentServicePrice: number
|
||
logisticsPrice: number
|
||
totalAmount: number
|
||
}
|
||
|
||
interface GoodsSupplyProduct {
|
||
id: string
|
||
name: string
|
||
sku: string
|
||
category: string
|
||
plannedQty: number
|
||
actualQty: number
|
||
defectQty: number
|
||
productPrice: number
|
||
parameters: ProductParameter[]
|
||
}
|
||
```
|
||
|
||
**Особенности:**
|
||
|
||
- Четырехуровневая структура: Поставка → Маршрут → Поставщик → Товар
|
||
- Детальная информация по каждому уровню
|
||
- Цветовая индикация статусов
|
||
- Расчет итоговых сумм на каждом уровне
|
||
- Поддержка параметров товаров
|
||
|
||
**Статусы поставок:**
|
||
|
||
```typescript
|
||
type SupplyStatus =
|
||
| 'new' // Новая
|
||
| 'confirmed' // Подтверждена
|
||
| 'in_transit' // В пути
|
||
| 'at_fulfillment' // На фулфилменте
|
||
| 'in_processing' // В обработке
|
||
| 'completed' // Завершена
|
||
| 'cancelled' // Отменена
|
||
| 'issue' // Проблема
|
||
```
|
||
|
||
**Использование:**
|
||
|
||
```typescript
|
||
<GoodsSuppliesTable
|
||
supplies={goodsSupplies}
|
||
loading={isLoading}
|
||
onActionClick={(id, action) => {
|
||
if (action === 'view') navigateToDetails(id)
|
||
if (action === 'cancel') cancelSupply(id)
|
||
}}
|
||
/>
|
||
```
|
||
|
||
## 🎨 ДИЗАЙН-СИСТЕМА КОМПОНЕНТОВ
|
||
|
||
### Унифицированные props:
|
||
|
||
```typescript
|
||
// Большинство компонентов поддерживают:
|
||
interface CommonProps {
|
||
className?: string // Дополнительные CSS классы
|
||
asChild?: boolean // Использование как Slot от Radix
|
||
'data-slot'?: string // Автоматический слот для идентификации
|
||
}
|
||
```
|
||
|
||
### Паттерн CVA (Class Variance Authority):
|
||
|
||
```typescript
|
||
const componentVariants = cva(
|
||
'базовые-классы', // Общие стили для всех вариантов
|
||
{
|
||
variants: {
|
||
variant: {
|
||
// Варианты дизайна
|
||
default: 'стили-по-умолчанию',
|
||
secondary: 'вторичные-стили',
|
||
},
|
||
size: {
|
||
// Размеры
|
||
sm: 'маленький-размер',
|
||
lg: 'большой-размер',
|
||
},
|
||
},
|
||
defaultVariants: {
|
||
// Значения по умолчанию
|
||
variant: 'default',
|
||
size: 'default',
|
||
},
|
||
},
|
||
)
|
||
```
|
||
|
||
### Accessibility Features:
|
||
|
||
- **ARIA Support** - все компоненты поддерживают ARIA атрибуты
|
||
- **Keyboard Navigation** - полная навигация с клавиатуры
|
||
- **Focus Management** - логичное управление фокусом
|
||
- **Screen Reader** - совместимость с программами чтения экрана
|
||
|
||
### Glass Morphism Effects:
|
||
|
||
```css
|
||
.glass-card {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
backdrop-filter: blur(20px);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
box-shadow: 0 8px 32px rgba(168, 85, 247, 0.18);
|
||
}
|
||
|
||
.glass-input {
|
||
background: rgba(255, 255, 255, 0.08);
|
||
backdrop-filter: blur(12px);
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
}
|
||
|
||
.glass-button {
|
||
background: linear-gradient(135deg, rgba(168, 85, 247, 0.9) 0%, rgba(59, 130, 246, 0.85) 100%);
|
||
backdrop-filter: blur(20px);
|
||
}
|
||
```
|
||
|
||
## 🔧 ПРАВИЛА ИСПОЛЬЗОВАНИЯ
|
||
|
||
### 1. Типизация компонентов
|
||
|
||
```typescript
|
||
// ✅ Правильно - с типизацией
|
||
<Button variant="glass" size="lg" onClick={handleClick}>
|
||
Действие
|
||
</Button>
|
||
|
||
// ❌ Неправильно - без типизации
|
||
<button className="some-custom-class">
|
||
Действие
|
||
</button>
|
||
```
|
||
|
||
### 2. Композиция сложных компонентов
|
||
|
||
```typescript
|
||
// ✅ Правильно - составная структура
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Заказ #1234</CardTitle>
|
||
<CardAction>
|
||
<Button size="sm">Детали</Button>
|
||
</CardAction>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p>Описание заказа</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
// ❌ Неправильно - плоская структура
|
||
<div className="card">
|
||
<h3>Заказ #1234</h3>
|
||
<p>Описание заказа</p>
|
||
</div>
|
||
```
|
||
|
||
### 3. Glass Morphism для темных фонов
|
||
|
||
```typescript
|
||
// ✅ Правильно - Glass компоненты на темном фоне
|
||
<div className="bg-gradient-cosmic">
|
||
<GlassInput placeholder="Поиск..." />
|
||
<Button variant="glass">Найти</Button>
|
||
</div>
|
||
|
||
// ❌ Неправильно - обычные компоненты на темном фоне
|
||
<div className="bg-black">
|
||
<Input placeholder="Поиск..." /> {/* Не видно */}
|
||
</div>
|
||
```
|
||
|
||
### 4. Accessibility обязателен
|
||
|
||
```typescript
|
||
// ✅ Правильно - с accessibility
|
||
<Label htmlFor="email">Email</Label>
|
||
<Input
|
||
id="email"
|
||
type="email"
|
||
aria-describedby="email-error"
|
||
aria-invalid={hasError}
|
||
/>
|
||
{hasError && <span id="email-error">Неверный формат email</span>}
|
||
|
||
// ❌ Неправильно - без accessibility
|
||
<span>Email</span>
|
||
<input type="email" />
|
||
```
|
||
|
||
### 5. Состояния загрузки
|
||
|
||
```typescript
|
||
// ✅ Правильно - скелетоны для загрузки
|
||
{loading ? (
|
||
<ProductCardSkeleton />
|
||
) : (
|
||
<ProductCard data={product} />
|
||
)}
|
||
|
||
// ❌ Неправильно - пустая область
|
||
{loading ? null : <ProductCard data={product} />}
|
||
```
|
||
|
||
## 🏪 КОМПОНЕНТЫ КАБИНЕТА ПОСТАВЩИКА (WHOLESALE)
|
||
|
||
### АРХИТЕКТУРА КОМПОНЕНТОВ ПОСТАВЩИКА:
|
||
|
||
```typescript
|
||
src/components/
|
||
├── warehouse/ # Компоненты склада поставщика
|
||
│ ├── warehouse-dashboard.tsx # Главный dashboard склада
|
||
│ ├── product-card.tsx # Карточка товара
|
||
│ ├── product-form.tsx # Форма создания/редактирования товара
|
||
│ └── warehouse-statistics.tsx # Статистика склада
|
||
├── supplier-orders/ # Компоненты обработки заказов
|
||
│ ├── supplier-orders-dashboard.tsx # Главный dashboard заказов
|
||
│ ├── supplier-order-card.tsx # Карточка заказа
|
||
│ ├── supplier-orders-tabs.tsx # Табы по статусам заказов
|
||
│ ├── supplier-orders-search.tsx # Поиск и фильтры
|
||
│ └── supplier-order-stats.tsx # Статистика заказов
|
||
└── economics/ # Экономическая аналитика
|
||
└── wholesale-economics-page.tsx # Финансовая отчетность
|
||
```
|
||
|
||
### 🏢 КАРТОЧКА ПОСТАВЩИКА В ИНТЕРФЕЙСЕ:
|
||
|
||
**Структура карточки:**
|
||
|
||
```jsx
|
||
<div className="supplier-card glass-card">
|
||
<div className="flex items-start gap-2">
|
||
{/* Аватар организации */}
|
||
<OrganizationAvatar organization={supplier} size="sm" />
|
||
|
||
<div className="flex-1 min-w-0">
|
||
{/* Название поставщика */}
|
||
<h4 className="text-white font-medium text-sm truncate">{supplier.name || supplier.fullName}</h4>
|
||
|
||
{/* ИНН и рынок */}
|
||
<div className="flex items-center gap-2 mt-1">
|
||
<p className="text-white/60 text-xs font-mono">ИНН: {supplier.inn}</p>
|
||
{supplier.market && <Badge className="market-badge">{getMarketLabel(supplier.market)}</Badge>}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**Визуальные правила карточки поставщика:**
|
||
|
||
- **Аватар**: Размер `sm`, позиционирование слева от текста
|
||
- **Название**: Приоритет `name` над `fullName`, с усечением `truncate`
|
||
- **ИНН**: Моноширинный шрифт `font-mono`, цвет `text-white/60`
|
||
- **Рынок**: Badge компонент с индивидуальными цветовыми схемами
|
||
- **Glass эффект**: `glass-card` класс с полупрозрачным фоном
|
||
|
||
### 🔍 ПОИСКОВЫЙ ИНТЕРФЕЙС ПОСТАВЩИКОВ:
|
||
|
||
```jsx
|
||
<Input
|
||
placeholder="Поиск поставщиков..."
|
||
className="bg-white/5 border-white/10 text-white placeholder:text-white/50 pl-10 h-9"
|
||
onChange={(e) => handleSupplierSearch(e.target.value)}
|
||
/>
|
||
```
|
||
|
||
**Особенности поиска:**
|
||
|
||
- Glass эффект: `bg-white/5 border-white/10`
|
||
- Плейсхолдер: `placeholder:text-white/50`
|
||
- Левый отступ для иконки: `pl-10`
|
||
- Высота: `h-9` (36px)
|
||
|
||
### 🎨 ЦВЕТОВЫЕ СХЕМЫ РЫНКОВ ПОСТАВЩИКОВ:
|
||
|
||
```typescript
|
||
// Примеры цветовых схем для физических рынков
|
||
const marketColors = {
|
||
sadovod: 'bg-green-500/20 text-green-300 border-green-500/30',
|
||
'tyak-moscow': 'bg-blue-500/20 text-blue-300 border-blue-500/30',
|
||
default: 'bg-gray-500/20 text-gray-300 border-gray-500/30',
|
||
}
|
||
|
||
// Функция получения метки рынка
|
||
function getMarketLabel(market: string): string {
|
||
const labels = {
|
||
sadovod: 'Садовод',
|
||
'tyak-moscow': 'ТЯК Москва',
|
||
default: 'Рынок',
|
||
}
|
||
return labels[market] || labels.default
|
||
}
|
||
```
|
||
|
||
### 📦 БЛОКИ ПОСТАВЩИКОВ В СЕЛЛЕР ИНТЕРФЕЙСЕ:
|
||
|
||
**Правила горизонтальной прокрутки:**
|
||
|
||
```jsx
|
||
{
|
||
/* Контейнер с горизонтальной прокруткой */
|
||
}
|
||
;<div className="flex gap-3 overflow-x-auto scrollbar-hide pb-2">
|
||
{suppliers.map((supplier) => (
|
||
<div
|
||
key={supplier.id}
|
||
className="flex-none w-64" // Фиксированная ширина 256px
|
||
>
|
||
<SupplierCard supplier={supplier} />
|
||
</div>
|
||
))}
|
||
</div>
|
||
```
|
||
|
||
**Требования к горизонтальным блокам:**
|
||
|
||
- Фиксированная ширина карточек: `w-64` (256px)
|
||
- Отсутствие сжатия: `flex-none`
|
||
- Скрытие скроллбара: `scrollbar-hide`
|
||
- Отступ от низа: `pb-2` для визуального комфорта
|
||
|
||
### 🚨 CRITICAL UI RULES ДЛЯ ПОСТАВЩИКОВ:
|
||
|
||
#### **1. СТАТУСЫ vs КНОПКИ ДЕЙСТВИЙ:**
|
||
|
||
```jsx
|
||
{
|
||
/* ❌ НЕПРАВИЛЬНО: Показывать статус поставщику */
|
||
}
|
||
{
|
||
user.organization.type === 'WHOLESALE' && <StatusBadge status={order.status}>Ожидает подтверждения</StatusBadge>
|
||
}
|
||
|
||
{
|
||
/* ✅ ПРАВИЛЬНО: Только кнопки действий для поставщика */
|
||
}
|
||
{
|
||
user.organization.type === 'WHOLESALE' && order.status === 'PENDING' && (
|
||
<div className="flex gap-2">
|
||
<Button variant="glass" size="sm" onClick={() => approveOrder(order.id)}>
|
||
Одобрить
|
||
</Button>
|
||
<Button variant="outline" size="sm" onClick={() => rejectOrder(order.id)}>
|
||
Отклонить
|
||
</Button>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
#### **2. ОПЦИОНАЛЬНЫЕ ПАРАМЕТРЫ ПОСТАВКИ ПРИ ОДОБРЕНИИ:**
|
||
|
||
```jsx
|
||
{
|
||
/* ОПЦИОНАЛЬНЫЕ параметры поставки для поставщика - отображаются при одобрении заказа */
|
||
}
|
||
;<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<Label htmlFor="packagesCount">Количество грузовых мест</Label>
|
||
<Input
|
||
id="packagesCount"
|
||
type="number"
|
||
placeholder="Введите количество (опционально)"
|
||
aria-describedby="packages-help"
|
||
/>
|
||
<p id="packages-help" className="text-xs text-white/60 mt-1">
|
||
Параметр поставки для логистических расчетов
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="volume">Объем груза (м³)</Label>
|
||
<Input id="volume" type="number" step="0.01" placeholder="0.00 (опционально)" aria-describedby="volume-help" />
|
||
<p id="volume-help" className="text-xs text-white/60 mt-1">
|
||
Параметр поставки для планирования маршрутов
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="deliveryDate">Дата поставки</Label>
|
||
<GlassDatePicker
|
||
id="deliveryDate"
|
||
placeholder="Выберите дату поставки"
|
||
aria-describedby="delivery-help"
|
||
/>
|
||
<p id="delivery-help" className="text-xs text-white/60 mt-1">
|
||
Основной параметр поставки - когда товары должны быть доставлены
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="totalAmount">Общая стоимость товаров</Label>
|
||
<Input id="totalAmount" type="number" readOnly className="bg-white/5" />
|
||
<p className="text-xs text-white/60 mt-1">
|
||
Ключевой параметр поставки - автоматически рассчитывается
|
||
</p>
|
||
</div>
|
||
|
||
<div className="col-span-2">
|
||
<Label htmlFor="readyDate">Дата готовности к отгрузке</Label>
|
||
<GlassDatePicker
|
||
id="readyDate"
|
||
value={readyDate}
|
||
onChange={setReadyDate}
|
||
placeholder="Выберите дату (опционально)"
|
||
aria-describedby="ready-date-help"
|
||
/>
|
||
<p id="ready-date-help" className="text-xs text-white/60 mt-1">
|
||
Когда товары будут готовы к передаче логистике
|
||
</p>
|
||
</div>
|
||
|
||
<div className="col-span-2">
|
||
<Label htmlFor="notes">Комментарии для логистики</Label>
|
||
<Textarea id="notes" placeholder="Дополнительная информация (опционально)" aria-describedby="notes-help" />
|
||
<p id="notes-help" className="text-xs text-white/60 mt-1">
|
||
Особые требования к транспортировке или упаковке
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{
|
||
/* ВАЖНО: Поля показываются на 1-м уровне визуализации поставки */
|
||
}
|
||
;<div className="mt-4">
|
||
<p className="text-sm text-white/80">ℹ️ Все поля опциональны, но рекомендуются для точного планирования логистики</p>
|
||
</div>
|
||
```
|
||
|
||
#### **3. ARIA LABELS ДЛЯ КОМПОНЕНТОВ ПОСТАВЩИКА:**
|
||
|
||
```jsx
|
||
// Кнопки действий с описательными ARIA-атрибутами
|
||
<Button
|
||
variant="glass"
|
||
aria-label={`Одобрить заказ №${order.number} от ${order.organization.name}`}
|
||
onClick={() => approveOrder(order.id)}
|
||
>
|
||
Одобрить
|
||
</Button>
|
||
|
||
// Поля ввода с полными описаниями
|
||
<Input
|
||
aria-label="Количество грузовых мест для логистического расчета"
|
||
aria-required="true"
|
||
aria-describedby="packages-error packages-help"
|
||
/>
|
||
```
|
||
|
||
#### **4. СПЕЦИАЛЬНЫЕ РАЗМЕРЫ ДЛЯ КАБИНЕТА ПОСТАВЩИКА:**
|
||
|
||
```typescript
|
||
// Размеры карточек в кабинете поставщика
|
||
const wholesaleSizes = {
|
||
supplierCard: 'h-[164px] w-64', // 164px высота, 256px ширина
|
||
orderCard: 'min-h-[120px]', // Минимум 120px для заказов
|
||
productCard: 'h-[180px]', // 180px для товарных карточек
|
||
containerWithPadding: 'h-[196px]', // 164 + 32px отступы сверху/снизу
|
||
}
|
||
```
|
||
|
||
### 📐 ФОРМУЛА РАСЧЕТА РАЗМЕРОВ КОНТЕЙНЕРОВ:
|
||
|
||
```typescript
|
||
// ОБЯЗАТЕЛЬНАЯ формула для всех контейнеров поставщика
|
||
const containerHeight = {
|
||
formula: 'Высота контента + padding-top + padding-bottom',
|
||
example: {
|
||
content: '164px', // Высота карточки поставщика
|
||
paddingTop: '16px',
|
||
paddingBottom: '16px',
|
||
totalContainer: '196px' // 164 + 16 + 16 = 196px
|
||
}
|
||
}
|
||
|
||
// ❌ ЗАПРЕЩЕНО: Произвольные размеры без расчета
|
||
<div className="h-200"> {/* Откуда 200px? */}
|
||
|
||
// ✅ ПРАВИЛЬНО: С математическим обоснованием
|
||
<div className="h-[196px]"> {/* 164px + 32px отступы */}
|
||
```
|
||
|
||
## 📱 АДАПТИВНОСТЬ
|
||
|
||
### Responsive Breakpoints:
|
||
|
||
- **`sm`** - `640px` и выше
|
||
- **`md`** - `768px` и выше
|
||
- **`lg`** - `1024px` и выше
|
||
- **`xl`** - `1280px` и выше
|
||
|
||
### Mobile-First подход:
|
||
|
||
```typescript
|
||
// ✅ Правильно
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
||
{items.map(item => <Card key={item.id}>{item.name}</Card>)}
|
||
</div>
|
||
|
||
// ❌ Неправильно
|
||
<div className="grid-cols-3"> {/* Не адаптивно */}
|
||
```
|
||
|
||
## 🚀 ПРОИЗВОДИТЕЛЬНОСТЬ
|
||
|
||
### Lazy Loading компонентов:
|
||
|
||
```typescript
|
||
const HeavyComponent = lazy(() => import('./heavy-component'))
|
||
|
||
// Использование с Suspense
|
||
<Suspense fallback={<LoadingFallback />}>
|
||
<HeavyComponent />
|
||
</Suspense>
|
||
```
|
||
|
||
### Мемоизация дорогих вычислений:
|
||
|
||
```typescript
|
||
const ExpensiveComponent = memo(({ data }) => {
|
||
const processedData = useMemo(() =>
|
||
processLargeDataset(data), [data]
|
||
)
|
||
|
||
return <Chart data={processedData} />
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
_UI компоненты задокументированы на основе анализа 36 файлов в src/components/ui/_
|
||
_Версия документа: 2025-08-21_
|
||
_Основа: Radix UI + CVA + Tailwind CSS + Glass Morphism_
|