simpse-demo-fixed / src /components /DynamicPricingModal.tsx
Simeon G
Deploy DataBeast to Hugging Face Spaces
aa72857
Raw
History Blame Contribute Delete
14.5 kB
"use client"
import { useState, useEffect, useRef } from 'react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { EnhancedCard, EnhancedCardContent, EnhancedCardHeader, EnhancedCardTitle } from '@/components/ui/enhanced-card'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import {
TrendingUp,
TrendingDown,
BarChart3,
Activity,
Clock,
MapPin,
Building2,
DollarSign,
Zap,
Target,
Users,
Eye,
X,
Maximize2,
Minimize2
} from 'lucide-react'
export interface PriceData {
timestamp: Date
price: number
change: number
changePercent: number
volume?: number
confidence: 'high' | 'medium' | 'low'
}
export interface MarketMetric {
id: string
name: string
value: number
change: number
changePercent: number
trend: 'up' | 'down' | 'stable'
unit: string
category: 'price' | 'volume' | 'inventory' | 'demand'
}
interface DynamicPricingModalProps {
isOpen: boolean
onClose: () => void
propertyId: string
propertyName: string
propertyAddress: string
currentPrice: number
priceHistory: PriceData[]
marketMetrics: MarketMetric[]
className?: string
}
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(value)
}
const formatNumber = (value: number, unit: string) => {
if (unit === '%') return `${value.toFixed(1)}%`
if (unit === 'units') return value.toLocaleString()
if (unit === 'days') return `${value} days`
return value.toLocaleString()
}
const getConfidenceColor = (confidence: 'high' | 'medium' | 'low') => {
switch (confidence) {
case 'high': return 'text-green-600 bg-green-50'
case 'medium': return 'text-yellow-600 bg-yellow-50'
case 'low': return 'text-red-600 bg-red-50'
}
}
const getTrendIcon = (trend: 'up' | 'down' | 'stable') => {
switch (trend) {
case 'up': return TrendingUp
case 'down': return TrendingDown
case 'stable': return Activity
}
}
const getTrendColor = (trend: 'up' | 'down' | 'stable') => {
switch (trend) {
case 'up': return 'text-green-600'
case 'down': return 'text-red-600'
case 'stable': return 'text-gray-600'
}
}
export default function DynamicPricingModal({
isOpen,
onClose,
propertyId,
propertyName,
propertyAddress,
currentPrice,
priceHistory,
marketMetrics,
className
}: DynamicPricingModalProps) {
const [isExpanded, setIsExpanded] = useState(false)
const [selectedTimeframe, setSelectedTimeframe] = useState<'1D' | '1W' | '1M' | '3M' | '1Y'>('1D')
const [livePrice, setLivePrice] = useState(currentPrice)
const [priceChange, setPriceChange] = useState(0)
const [isAnimating, setIsAnimating] = useState(false)
const chartRef = useRef<HTMLDivElement>(null)
// Simulate live price updates
useEffect(() => {
if (!isOpen) return
const interval = setInterval(() => {
const variation = (Math.random() - 0.5) * 0.02 // ±1% variation
const newPrice = currentPrice * (1 + variation)
const change = newPrice - livePrice
setIsAnimating(true)
setLivePrice(newPrice)
setPriceChange(change)
setTimeout(() => setIsAnimating(false), 500)
}, 3000)
return () => clearInterval(interval)
}, [isOpen, currentPrice, livePrice])
const latestData = priceHistory[priceHistory.length - 1]
const changePercent = ((livePrice - currentPrice) / currentPrice) * 100
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
className={`${isExpanded ? 'max-w-6xl' : 'max-w-4xl'} transition-all duration-300 ${className}`}
>
<DialogHeader className="border-b border-gray-200 pb-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="p-2 bg-blue-100 rounded-full">
<Building2 className="h-5 w-5 text-blue-600" />
</div>
<div>
<DialogTitle className="text-xl font-bold text-gray-900">
{propertyName}
</DialogTitle>
<p className="text-sm text-gray-600 flex items-center mt-1">
<MapPin className="h-3 w-3 mr-1" />
{propertyAddress}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? (
<Minimize2 className="h-4 w-4" />
) : (
<Maximize2 className="h-4 w-4" />
)}
</Button>
<Button variant="outline" size="sm" onClick={onClose}>
<X className="h-4 w-4" />
</Button>
</div>
</div>
</DialogHeader>
<div className="grid grid-cols-12 gap-6 py-6">
{/* Live Price Display */}
<div className="col-span-12 lg:col-span-4">
<EnhancedCard variant="floating" className="text-center">
<EnhancedCardHeader>
<div className="flex items-center justify-center space-x-2 mb-2">
<Zap className="h-4 w-4 text-yellow-500 animate-pulse" />
<span className="text-sm text-gray-600 font-medium">LIVE PRICE</span>
<Badge variant="destructive" className="animate-pulse">
LIVE
</Badge>
</div>
</EnhancedCardHeader>
<EnhancedCardContent>
<div className={`text-4xl font-bold mb-2 transition-all duration-500 ${
isAnimating ? 'scale-105 text-blue-600' : 'text-gray-900'
}`}>
{formatCurrency(livePrice)}
</div>
<div className={`flex items-center justify-center space-x-2 text-lg ${
changePercent >= 0 ? 'text-green-600' : 'text-red-600'
}`}>
{changePercent >= 0 ? (
<TrendingUp className="h-4 w-4" />
) : (
<TrendingDown className="h-4 w-4" />
)}
<span>{formatCurrency(Math.abs(priceChange))}</span>
<span>({Math.abs(changePercent).toFixed(2)}%)</span>
</div>
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
<div className="text-center p-3 bg-blue-50 rounded-lg">
<div className="font-semibold text-blue-900">24h High</div>
<div className="text-blue-600">
{formatCurrency(currentPrice * 1.05)}
</div>
</div>
<div className="text-center p-3 bg-purple-50 rounded-lg">
<div className="font-semibold text-purple-900">24h Low</div>
<div className="text-purple-600">
{formatCurrency(currentPrice * 0.95)}
</div>
</div>
</div>
<div className="mt-4">
<div className={`text-xs px-2 py-1 rounded-full inline-flex items-center ${
getConfidenceColor(latestData?.confidence || 'medium')
}`}>
<Target className="h-3 w-3 mr-1" />
{latestData?.confidence.toUpperCase()} CONFIDENCE
</div>
</div>
</EnhancedCardContent>
</EnhancedCard>
</div>
{/* Price Chart Visualization */}
<div className="col-span-12 lg:col-span-8">
<EnhancedCard variant="elevated">
<EnhancedCardHeader>
<div className="flex items-center justify-between">
<EnhancedCardTitle className="flex items-center space-x-2">
<BarChart3 className="h-5 w-5" />
<span>Price Movement</span>
</EnhancedCardTitle>
<div className="flex space-x-1">
{(['1D', '1W', '1M', '3M', '1Y'] as const).map((timeframe) => (
<Button
key={timeframe}
variant={selectedTimeframe === timeframe ? 'default' : 'outline'}
size="xs"
onClick={() => setSelectedTimeframe(timeframe)}
className="px-2 py-1 text-xs"
>
{timeframe}
</Button>
))}
</div>
</div>
</EnhancedCardHeader>
<EnhancedCardContent>
{/* Simplified chart visualization */}
<div ref={chartRef} className="h-48 bg-gray-50 rounded-lg p-4 flex items-end space-x-1 overflow-hidden">
{priceHistory.slice(-20).map((data, index) => {
const height = ((data.price - Math.min(...priceHistory.map(d => d.price))) /
(Math.max(...priceHistory.map(d => d.price)) - Math.min(...priceHistory.map(d => d.price)))) * 160 + 20
return (
<div
key={index}
className={`flex-1 rounded-t transition-all duration-300 ${
data.change >= 0 ? 'bg-green-400' : 'bg-red-400'
}`}
style={{ height: `${height}px` }}
title={`${formatCurrency(data.price)} (${data.changePercent > 0 ? '+' : ''}${data.changePercent.toFixed(2)}%)`}
/>
)
})}
{/* Live price indicator */}
<div
className={`w-2 rounded-t bg-blue-500 animate-pulse transition-all duration-500 ${
isAnimating ? 'shadow-lg shadow-blue-300' : ''
}`}
style={{ height: '140px' }}
/>
</div>
<div className="mt-4 flex items-center justify-between text-xs text-gray-600">
<span>Past 20 intervals</span>
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-1">
<div className="w-3 h-3 bg-green-400 rounded"></div>
<span>Price Up</span>
</div>
<div className="flex items-center space-x-1">
<div className="w-3 h-3 bg-red-400 rounded"></div>
<span>Price Down</span>
</div>
<div className="flex items-center space-x-1">
<div className="w-3 h-3 bg-blue-500 rounded animate-pulse"></div>
<span>Live</span>
</div>
</div>
</div>
</EnhancedCardContent>
</EnhancedCard>
</div>
{/* Market Metrics */}
<div className="col-span-12">
<EnhancedCard variant="elevated">
<EnhancedCardHeader>
<EnhancedCardTitle className="flex items-center space-x-2">
<Activity className="h-5 w-5" />
<span>Live Market Metrics</span>
</EnhancedCardTitle>
</EnhancedCardHeader>
<EnhancedCardContent>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{marketMetrics.map((metric) => {
const TrendIcon = getTrendIcon(metric.trend)
const trendColor = getTrendColor(metric.trend)
return (
<div
key={metric.id}
className="p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
>
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700">{metric.name}</h4>
<TrendIcon className={`h-3 w-3 ${trendColor}`} />
</div>
<div className="text-lg font-bold text-gray-900 mb-1">
{formatNumber(metric.value, metric.unit)}
</div>
<div className={`text-xs flex items-center ${
metric.changePercent >= 0 ? 'text-green-600' : 'text-red-600'
}`}>
<span>
{metric.changePercent >= 0 ? '+' : ''}
{metric.changePercent.toFixed(1)}%
</span>
<span className="ml-1 text-gray-500">vs yesterday</span>
</div>
</div>
)
})}
</div>
</EnhancedCardContent>
</EnhancedCard>
</div>
{/* Quick Actions */}
<div className="col-span-12 flex items-center justify-between pt-4 border-t border-gray-200">
<div className="flex items-center space-x-2 text-sm text-gray-600">
<Clock className="h-4 w-4" />
<span>Last updated: {new Date().toLocaleTimeString()}</span>
<Badge variant="secondary" className="ml-2">
Auto-refresh: 3s
</Badge>
</div>
<div className="flex space-x-2">
<Button variant="outline" size="sm">
<Eye className="h-3 w-3 mr-1" />
Watch Property
</Button>
<Button size="sm">
<Users className="h-3 w-3 mr-1" />
Contact Agent
</Button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}