Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.
This commit is contained in:
431
src/components/market/organization-details-modal.tsx
Normal file
431
src/components/market/organization-details-modal.tsx
Normal file
@ -0,0 +1,431 @@
|
||||
"use client"
|
||||
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import {
|
||||
Building2,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Calendar,
|
||||
FileText,
|
||||
Users,
|
||||
CreditCard,
|
||||
Hash,
|
||||
User,
|
||||
Briefcase
|
||||
} from 'lucide-react'
|
||||
import { OrganizationAvatar } from './organization-avatar'
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
avatar?: string | null
|
||||
phone: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
interface ApiKey {
|
||||
id: string
|
||||
marketplace: string
|
||||
isActive: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
interface Organization {
|
||||
id: string
|
||||
inn: string
|
||||
kpp?: string | null
|
||||
name?: string | null
|
||||
fullName?: string | null
|
||||
type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
|
||||
address?: string | null
|
||||
addressFull?: string | null
|
||||
ogrn?: string | null
|
||||
ogrnDate?: string | null
|
||||
status?: string | null
|
||||
actualityDate?: string | null
|
||||
registrationDate?: string | null
|
||||
liquidationDate?: string | null
|
||||
managementName?: string | null
|
||||
managementPost?: string | null
|
||||
opfCode?: string | null
|
||||
opfFull?: string | null
|
||||
opfShort?: string | null
|
||||
okato?: string | null
|
||||
oktmo?: string | null
|
||||
okpo?: string | null
|
||||
okved?: string | null
|
||||
employeeCount?: number | null
|
||||
revenue?: string | null
|
||||
taxSystem?: string | null
|
||||
phones?: Array<{ value: string }> | null
|
||||
emails?: Array<{ value: string }> | null
|
||||
users?: User[]
|
||||
apiKeys?: ApiKey[]
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
interface OrganizationDetailsModalProps {
|
||||
organization: Organization | null
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
function formatDate(dateString?: string | null): string {
|
||||
if (!dateString) return 'Не указана'
|
||||
|
||||
try {
|
||||
let date: Date
|
||||
|
||||
// Проверяем, является ли строка числом (Unix timestamp)
|
||||
if (/^\d+$/.test(dateString)) {
|
||||
// Если это Unix timestamp в миллисекундах
|
||||
const timestamp = parseInt(dateString, 10)
|
||||
date = new Date(timestamp)
|
||||
} else {
|
||||
// Обычная строка даты
|
||||
date = new Date(dateString)
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Не указана'
|
||||
}
|
||||
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
} catch (error) {
|
||||
return 'Не указана'
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeLabel(type: string): string {
|
||||
switch (type) {
|
||||
case 'FULFILLMENT':
|
||||
return 'Фулфилмент'
|
||||
case 'SELLER':
|
||||
return 'Селлер'
|
||||
case 'LOGIST':
|
||||
return 'Логистика'
|
||||
case 'WHOLESALE':
|
||||
return 'Оптовик'
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeColor(type: string): string {
|
||||
switch (type) {
|
||||
case 'FULFILLMENT':
|
||||
return 'bg-blue-500/20 text-blue-300 border-blue-500/30'
|
||||
case 'SELLER':
|
||||
return 'bg-green-500/20 text-green-300 border-green-500/30'
|
||||
case 'LOGIST':
|
||||
return 'bg-orange-500/20 text-orange-300 border-orange-500/30'
|
||||
case 'WHOLESALE':
|
||||
return 'bg-purple-500/20 text-purple-300 border-purple-500/30'
|
||||
default:
|
||||
return 'bg-gray-500/20 text-gray-300 border-gray-500/30'
|
||||
}
|
||||
}
|
||||
|
||||
export function OrganizationDetailsModal({ organization, open, onOpenChange }: OrganizationDetailsModalProps) {
|
||||
if (!organization) return null
|
||||
|
||||
const displayName = organization.name || organization.fullName || 'Неизвестная организация'
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto bg-black/90 backdrop-blur-xl border border-white/20">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center space-x-4 text-white">
|
||||
<OrganizationAvatar organization={organization} size="lg" />
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">{displayName}</h2>
|
||||
<Badge className={getTypeColor(organization.type)}>
|
||||
{getTypeLabel(organization.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Основная информация */}
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<Building2 className="h-5 w-5 mr-2 text-blue-400" />
|
||||
Основная информация
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">ИНН:</span>
|
||||
<span className="text-white font-mono">{organization.inn}</span>
|
||||
</div>
|
||||
|
||||
{organization.kpp && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">КПП:</span>
|
||||
<span className="text-white font-mono">{organization.kpp}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.ogrn && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">ОГРН:</span>
|
||||
<span className="text-white font-mono">{organization.ogrn}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.status && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Статус:</span>
|
||||
<span className="text-white">{organization.status}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Дата регистрации:</span>
|
||||
<span className="text-white">{formatDate(organization.registrationDate)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Контактная информация */}
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<Phone className="h-5 w-5 mr-2 text-green-400" />
|
||||
Контакты
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.phones && organization.phones.length > 0 && (
|
||||
<div>
|
||||
<div className="text-white/60 text-sm mb-2">Телефоны:</div>
|
||||
{organization.phones.map((phone, index) => (
|
||||
<div key={index} className="flex items-center text-white">
|
||||
<Phone className="h-3 w-3 mr-2 text-green-400" />
|
||||
{phone.value}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.emails && organization.emails.length > 0 && (
|
||||
<div>
|
||||
<div className="text-white/60 text-sm mb-2">Email:</div>
|
||||
{organization.emails.map((email, index) => (
|
||||
<div key={index} className="flex items-center text-white">
|
||||
<Mail className="h-3 w-3 mr-2 text-blue-400" />
|
||||
{email.value}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.address && (
|
||||
<div>
|
||||
<div className="text-white/60 text-sm mb-2">Адрес:</div>
|
||||
<div className="flex items-start text-white">
|
||||
<MapPin className="h-3 w-3 mr-2 mt-1 text-orange-400 flex-shrink-0" />
|
||||
<span className="text-sm">{organization.addressFull || organization.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Руководство */}
|
||||
{organization.managementName && (
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<User className="h-5 w-5 mr-2 text-purple-400" />
|
||||
Руководство
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Руководитель:</span>
|
||||
<span className="text-white">{organization.managementName}</span>
|
||||
</div>
|
||||
|
||||
{organization.managementPost && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Должность:</span>
|
||||
<span className="text-white">{organization.managementPost}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Организационно-правовая форма */}
|
||||
{organization.opfFull && (
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<FileText className="h-5 w-5 mr-2 text-yellow-400" />
|
||||
ОПФ
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Полное название:</span>
|
||||
<span className="text-white">{organization.opfFull}</span>
|
||||
</div>
|
||||
|
||||
{organization.opfShort && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Краткое название:</span>
|
||||
<span className="text-white">{organization.opfShort}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.opfCode && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Код ОКОПФ:</span>
|
||||
<span className="text-white font-mono">{organization.opfCode}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Коды статистики */}
|
||||
{(organization.okato || organization.oktmo || organization.okpo || organization.okved) && (
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<Hash className="h-5 w-5 mr-2 text-cyan-400" />
|
||||
Коды статистики
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.okato && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">ОКАТО:</span>
|
||||
<span className="text-white font-mono">{organization.okato}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.oktmo && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">ОКТМО:</span>
|
||||
<span className="text-white font-mono">{organization.oktmo}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.okpo && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">ОКПО:</span>
|
||||
<span className="text-white font-mono">{organization.okpo}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.okved && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Основной ОКВЭД:</span>
|
||||
<span className="text-white font-mono">{organization.okved}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Финансовая информация */}
|
||||
{(organization.employeeCount || organization.revenue || organization.taxSystem) && (
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<CreditCard className="h-5 w-5 mr-2 text-emerald-400" />
|
||||
Финансовая информация
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.employeeCount && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Сотрудников:</span>
|
||||
<span className="text-white">{organization.employeeCount}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.revenue && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Выручка:</span>
|
||||
<span className="text-white">{organization.revenue}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{organization.taxSystem && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/60">Налоговая система:</span>
|
||||
<span className="text-white">{organization.taxSystem}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Пользователи */}
|
||||
{organization.users && organization.users.length > 0 && (
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<Users className="h-5 w-5 mr-2 text-indigo-400" />
|
||||
Пользователи ({organization.users.length})
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.users.map((user, index) => (
|
||||
<div key={user.id} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<OrganizationAvatar
|
||||
organization={{
|
||||
id: user.id,
|
||||
users: [user]
|
||||
}}
|
||||
size="sm"
|
||||
/>
|
||||
<span className="text-white">{user.phone}</span>
|
||||
</div>
|
||||
<span className="text-white/60 text-sm">
|
||||
{formatDate(user.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* API ключи */}
|
||||
{organization.apiKeys && organization.apiKeys.length > 0 && (
|
||||
<Card className="glass-card p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
|
||||
<Briefcase className="h-5 w-5 mr-2 text-pink-400" />
|
||||
API ключи маркетплейсов
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{organization.apiKeys.map((apiKey, index) => (
|
||||
<div key={apiKey.id} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Badge className={apiKey.isActive ? 'bg-green-500/20 text-green-300 border-green-500/30' : 'bg-red-500/20 text-red-300 border-red-500/30'}>
|
||||
{apiKey.marketplace}
|
||||
</Badge>
|
||||
<span className="text-white/60 text-sm">
|
||||
{apiKey.isActive ? 'Активен' : 'Неактивен'}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-white/60 text-sm">
|
||||
{formatDate(apiKey.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user