'use client'; import { useMemo } from 'react'; import { Filter, ThumbsUp, ThumbsDown, TrendingUp, TrendingDown, Minus } from 'lucide-react'; import type { URTDomainPoint, URTDomain, Sentiment } from '../types'; import { useReviewIQFilters } from '@/contexts/ReviewIQFilterContext'; interface SentimentHeatmapProps { data: URTDomainPoint[]; // AI-generated insight (optional - shows when available) insight?: string | null; // Domain to highlight (optional - from AI priority) highlightDomain?: string | null; } // User-friendly domain config with emojis and descriptions const DOMAIN_CONFIG: Record = { P: { emoji: '👥', label: 'Staff & Service', description: 'How staff treats customers' }, V: { emoji: '💰', label: 'Pricing & Value', description: 'Price, fees, and value for money' }, J: { emoji: '⏱️', label: 'Speed & Process', description: 'Wait times and procedures' }, O: { emoji: '🛍️', label: 'Product Quality', description: 'Quality of goods/services' }, A: { emoji: '📍', label: 'Availability', description: 'Hours, location, accessibility' }, E: { emoji: '🏢', label: 'Facilities', description: 'Cleanliness, comfort, ambiance' }, R: { emoji: '🤝', label: 'Trust & Ethics', description: 'Honesty, reliability, fairness' }, }; // Ordered domains by typical business priority const DOMAIN_ORDER = ['P', 'V', 'J', 'O', 'A', 'E', 'R']; // Color scales const getPositiveColor = (value: number, max: number): string => { if (max === 0 || value === 0) return '#f3f4f6'; const intensity = value / max; if (intensity < 0.25) return '#dcfce7'; // Light green if (intensity < 0.5) return '#86efac'; if (intensity < 0.75) return '#22c55e'; return '#15803d'; // Dark green }; const getNegativeColor = (value: number, max: number): string => { if (max === 0 || value === 0) return '#f3f4f6'; const intensity = value / max; if (intensity < 0.25) return '#fee2e2'; // Light red if (intensity < 0.5) return '#fca5a5'; if (intensity < 0.75) return '#ef4444'; return '#b91c1c'; // Dark red }; /** * Sentiment Heatmap - Shows Praise vs Complaints by Domain. * User-friendly design with emojis and clear labels. * Click to filter by domain and sentiment. */ export function IntensityHeatmap({ data, insight, highlightDomain }: SentimentHeatmapProps) { const { filters, setURTDomain, toggleSentiment } = useReviewIQFilters(); // Check if cross-filters are active const hasSentimentFilter = filters.sentiment.length > 0; const hasDomainFilter = filters.urtDomain !== null; const hasCrossFilter = hasSentimentFilter || hasDomainFilter; // Process and sort data const processedData = useMemo(() => { // Create lookup map const lookup = new Map(); let maxPositive = 0; let maxNegative = 0; data.forEach((d) => { lookup.set(d.domain, d); if (d.positive_count > maxPositive) maxPositive = d.positive_count; if (d.negative_count > maxNegative) maxNegative = d.negative_count; }); // Sort by domain order, then build rows const rows = DOMAIN_ORDER .filter(domain => lookup.has(domain)) .map(domain => { const d = lookup.get(domain)!; const total = d.positive_count + d.negative_count + d.neutral_count; const positiveRatio = total > 0 ? d.positive_count / total : 0; const negativeRatio = total > 0 ? d.negative_count / total : 0; // Determine trend indicator let trend: 'up' | 'down' | 'neutral' = 'neutral'; if (positiveRatio > 0.6) trend = 'up'; else if (negativeRatio > 0.4) trend = 'down'; return { domain: d.domain, config: DOMAIN_CONFIG[d.domain] || { emoji: '📊', label: d.domain_name || d.domain, description: '' }, positive: d.positive_count, negative: d.negative_count, total, positiveRatio, negativeRatio, trend, }; }); return { rows, maxPositive, maxNegative }; }, [data]); const handleCellClick = (domain: string, sentiment: 'positive' | 'negative') => { setURTDomain(domain as URTDomain); // Clear other sentiments and set the clicked one if (!filters.sentiment.includes(sentiment)) { toggleSentiment(sentiment); } }; const handleDomainClick = (domain: string) => { setURTDomain(domain as URTDomain); }; return (

Feedback by Category

Click any cell to filter reviews

{hasCrossFilter && (
Filtered
)}
{/* AI Insight (when available) */} {insight && (
AI Insight

{insight}

)} {data.length === 0 ? (
No feedback data available
) : (
{processedData.rows.map((row) => { const isDomainActive = filters.urtDomain === row.domain; const isPositiveActive = isDomainActive && filters.sentiment.includes('positive'); const isNegativeActive = isDomainActive && filters.sentiment.includes('negative'); const isHighlighted = highlightDomain === row.domain; return ( {/* Domain Label */} {/* Praise Cell */} {/* Complaints Cell */} {/* Health Indicator */} ); })}
Category
Praise
Complaints
Health
{row.trend === 'up' && (
)} {row.trend === 'down' && (
)} {row.trend === 'neutral' && (
)}
{/* Legend */}
Praise scale:
Complaints scale:
= needs attention
)}
); }