krona-front/src/features/communications/components/ContactsView.tsx

163 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { Search, Plus, MessageSquare, Star, Clock } from 'lucide-react';
import { useCommunicationsStore } from '../services/communicationsService';
import { Badge, Button } from '@omnichannel/ui';
interface ContactsViewProps {
onAddContactModal: () => void;
}
export function ContactsView({ onAddContactModal }: ContactsViewProps) {
const { conversations, setActiveTab, setActiveChatId } = useCommunicationsStore();
const [filterType, setFilterType] = useState<'all' | 'clients' | 'vip'>('all');
const [searchQuery, setSearchQuery] = useState('');
const contactsList = conversations.map((c) => ({
...c.contact,
conversationId: c.id,
}));
const filteredContacts = contactsList.filter((cnt) => {
if (filterType === 'clients' && !cnt.isClient) return false;
if (filterType === 'vip' && !cnt.isVip) return false;
if (searchQuery.trim()) {
const q = searchQuery.toLowerCase();
return (
cnt.name.toLowerCase().includes(q) ||
cnt.company.toLowerCase().includes(q) ||
cnt.role.toLowerCase().includes(q)
);
}
return true;
});
const handleOpenChat = (conversationId: string) => {
setActiveChatId(conversationId);
setActiveTab('messengers');
};
return (
<div className="flex-1 min-h-0 flex flex-col p-4 sm:p-6 bg-[var(--krona-bg-workspace)] overflow-y-auto">
{/* Header controls */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
<div className="flex items-center gap-3">
<div className="relative w-64 sm:w-80">
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-[var(--krona-text-muted)]" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Поиск по контактам..."
className="w-full h-10 pl-9 pr-3 rounded-xl bg-[var(--krona-surface)] border border-[var(--krona-border)] text-sm text-[var(--krona-text-primary)] placeholder-[var(--krona-text-muted)] focus:outline-none focus:border-[var(--krona-primary)]"
/>
</div>
<div className="flex items-center gap-1 bg-[var(--krona-surface)] p-1 rounded-xl border border-[var(--krona-border)] text-xs font-semibold">
<button
type="button"
onClick={() => setFilterType('all')}
className={`px-3 py-1.5 rounded-lg transition-colors ${
filterType === 'all'
? 'bg-[var(--krona-primary)] text-white'
: 'text-[var(--krona-text-secondary)] hover:text-[var(--krona-text-primary)]'
}`}
>
Все
</button>
<button
type="button"
onClick={() => setFilterType('clients')}
className={`px-3 py-1.5 rounded-lg transition-colors ${
filterType === 'clients'
? 'bg-[var(--krona-primary)] text-white'
: 'text-[var(--krona-text-secondary)] hover:text-[var(--krona-text-primary)]'
}`}
>
Клиенты
</button>
<button
type="button"
onClick={() => setFilterType('vip')}
className={`px-3 py-1.5 rounded-lg transition-colors ${
filterType === 'vip'
? 'bg-[var(--krona-primary)] text-white'
: 'text-[var(--krona-text-secondary)] hover:text-[var(--krona-text-primary)]'
}`}
>
VIP
</button>
</div>
</div>
<Button
variant="primary"
onClick={onAddContactModal}
leftIcon={<Plus className="w-4 h-4" />}
>
Добавить контакт
</Button>
</div>
{/* Contacts Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{filteredContacts.map((cnt) => (
<div
key={cnt.id}
className="p-5 rounded-2xl bg-[var(--krona-surface)] border border-[var(--krona-border-subtle)] shadow-xs flex flex-col justify-between gap-4 hover:border-[var(--krona-border)] transition-all"
>
<div>
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] flex items-center justify-center font-bold text-sm text-[var(--krona-primary)]">
{cnt.initials}
</div>
<div>
<div className="flex items-center gap-1.5">
<h3 className="font-bold text-sm text-[var(--krona-text-primary)]">
{cnt.name}
</h3>
{cnt.isVip && <Star className="w-3.5 h-3.5 text-amber-500 fill-amber-500" />}
</div>
<span className="text-xs text-[var(--krona-text-secondary)] block">
{cnt.company} • {cnt.role}
</span>
</div>
</div>
<div className="flex items-center gap-1">
{cnt.isClient && <Badge variant="success" size="sm">Клиент</Badge>}
{cnt.isVip && <Badge variant="warning" size="sm">VIP</Badge>}
</div>
</div>
{/* Channels snippet */}
<div className="mt-4 space-y-1.5 text-xs text-[var(--krona-text-secondary)]">
{cnt.channels.slice(0, 2).map((ch, idx) => (
<div key={idx} className="flex items-center justify-between">
<span className="font-medium capitalize">{ch.label}:</span>
<span className="text-[var(--krona-text-primary)]">{ch.value}</span>
</div>
))}
</div>
</div>
<div className="pt-3 border-t border-[var(--krona-border-subtle)] flex items-center justify-between gap-2">
<span className="text-[11px] text-[var(--krona-text-muted)] flex items-center gap-1">
<Clock className="w-3 h-3" />
{cnt.lastActivity}
</span>
<Button
variant="secondary"
size="sm"
onClick={() => handleOpenChat(cnt.conversationId)}
leftIcon={<MessageSquare className="w-3.5 h-3.5 text-[var(--krona-primary)]" />}
>
В чат
</Button>
</div>
</div>
))}
</div>
</div>
);
}