| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- import * as React from 'react';
- import { AnalyticsData, Visitor, Conversation } from '../types';
- import { format, parseISO } from 'date-fns';
- import { Icon } from './ui/Icon';
- import { useTranslation } from '../hooks/useI18n';
- interface CRMProps {
- analyticsData: AnalyticsData;
- }
- export const CRM: React.FC<CRMProps> = ({ analyticsData }) => {
- const { t } = useTranslation();
- const [sortConfig, setSortConfig] = React.useState<{ key: keyof Visitor; direction: 'asc' | 'desc' } | null>({ key: 'lastSeen', direction: 'desc' });
- const visitors = React.useMemo((): Visitor[] => {
- const visitorMap = new Map<string, { lastSeen: Date; visits: Set<string>; conversationCount: number }>();
- analyticsData.conversations.forEach(conv => {
- if (!visitorMap.has(conv.visitorId)) {
- visitorMap.set(conv.visitorId, { lastSeen: new Date(0), visits: new Set(), conversationCount: 0 });
- }
- const data = visitorMap.get(conv.visitorId)!;
- const convDate = new Date(conv.timestamp);
- if (convDate > data.lastSeen) {
- data.lastSeen = convDate;
- }
- data.visits.add(conv.timestamp);
- data.conversationCount += 1;
- });
- const visitorList = Array.from(visitorMap.entries()).map(([id, data]) => ({
- id,
- lastSeen: data.lastSeen.toISOString(),
- visitCount: data.visits.size,
- conversationCount: data.conversationCount,
- }));
-
- return visitorList;
- }, [analyticsData.conversations]);
-
- const sortedVisitors = React.useMemo(() => {
- const sortableItems = [...visitors];
- if (sortConfig !== null) {
- sortableItems.sort((a, b) => {
- if (a[sortConfig.key] < b[sortConfig.key]) {
- return sortConfig.direction === 'asc' ? -1 : 1;
- }
- if (a[sortConfig.key] > b[sortConfig.key]) {
- return sortConfig.direction === 'asc' ? 1 : -1;
- }
- return 0;
- });
- }
- return sortableItems;
- }, [visitors, sortConfig]);
-
- const requestSort = (key: keyof Visitor) => {
- let direction: 'asc' | 'desc' = 'asc';
- if (sortConfig && sortConfig.key === key && sortConfig.direction === 'asc') {
- direction = 'desc';
- }
- setSortConfig({ key, direction });
- };
- const SortableHeader: React.FC<{ sortKey: keyof Visitor; children?: React.ReactNode }> = ({ sortKey, children }) => {
- const isSorted = sortConfig?.key === sortKey;
- const directionIcon = sortConfig?.direction === 'asc' ? '▲' : '▼';
- return (
- // FIX: The className was truncated. It has been completed.
- <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer" onClick={() => requestSort(sortKey)}>
- <div className="flex items-center">
- {children}
- {isSorted && <span className="ml-2">{directionIcon}</span>}
- </div>
- </th>
- );
- };
-
- // FIX: The component was not returning any JSX, causing a type error. A return statement with a table has been added.
- return (
- <div className="p-6 bg-white dark:bg-gray-800 rounded-lg">
- <h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">{t('analytics.crm.title')}</h2>
- <div className="overflow-x-auto">
- <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
- <thead className="bg-gray-50 dark:bg-gray-700">
- <tr>
- <SortableHeader sortKey="id">{t('analytics.crm.visitor_id')}</SortableHeader>
- <SortableHeader sortKey="lastSeen">{t('analytics.crm.last_seen')}</SortableHeader>
- <SortableHeader sortKey="visitCount">{t('analytics.crm.visits')}</SortableHeader>
- <SortableHeader sortKey="conversationCount">{t('analytics.interactions.conversations')}</SortableHeader>
- </tr>
- </thead>
- <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
- {sortedVisitors.map((visitor) => (
- <tr key={visitor.id}>
- <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-brand-primary">{visitor.id}</td>
- <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{format(parseISO(visitor.lastSeen), 'MMM d, yyyy h:mm a')}</td>
- <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{visitor.visitCount}</td>
- <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{visitor.conversationCount}</td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- </div>
- );
- };
|