add a little responsiveness

This commit is contained in:
tomato6966 2025-01-18 20:11:49 +01:00
parent 3629e5a1d9
commit 1adcad1855
2 changed files with 173 additions and 134 deletions

View file

@ -1,5 +1,5 @@
import { X } from "lucide-react"; import { X } from "lucide-react";
import { memo } from "react"; import { memo, useEffect } from "react";
import { import {
CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis
} from "recharts"; } from "recharts";
@ -13,6 +13,14 @@ interface AssetPerformanceModalProps {
export const AssetPerformanceModal = memo(({ assetName, performances, onClose }: AssetPerformanceModalProps) => { export const AssetPerformanceModal = memo(({ assetName, performances, onClose }: AssetPerformanceModalProps) => {
const sortedPerformances = [...performances].sort((a, b) => a.year - b.year); const sortedPerformances = [...performances].sort((a, b) => a.year - b.year);
// Prevent body scroll when modal is open
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = 'unset';
};
}, []);
const CustomizedDot = (props: any) => { const CustomizedDot = (props: any) => {
const { cx, cy, payload } = props; const { cx, cy, payload } = props;
return ( return (
@ -26,83 +34,94 @@ export const AssetPerformanceModal = memo(({ assetName, performances, onClose }:
}; };
return ( return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/50 z-50 md:flex md:items-center md:justify-center">
<div className="bg-white dark:bg-slate-800 p-6 rounded-lg w-[80vw] max-w-4xl"> <div className="h-full md:h-auto w-full md:w-[80vw] bg-white dark:bg-slate-800 md:rounded-lg overflow-hidden">
<div className="flex justify-between items-center mb-6"> {/* Header - Fixed */}
<h2 className="text-xl font-bold">{assetName} - Yearly Performance</h2> <div className="sticky top-0 z-10 bg-white dark:bg-slate-800 p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
<button onClick={onClose} className="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded"> <h2 className="text-xl font-bold dark:text-gray-300">{assetName} - Yearly Performance</h2>
<X className="w-6 h-6" /> <button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded"
>
<X className="w-6 h-6 dark:text-gray-300" />
</button> </button>
</div> </div>
<div className="h-[400px]">
<ResponsiveContainer> {/* Content - Scrollable */}
<LineChart data={sortedPerformances}> <div className="overflow-y-auto h-[calc(100vh-64px)] md:h-[calc(80vh-64px)] p-6">
<CartesianGrid strokeDasharray="3 3" /> {/* Chart */}
<XAxis dataKey="year" /> <div className="h-[400px] mb-6">
<YAxis <ResponsiveContainer>
yAxisId="left" <LineChart data={sortedPerformances}>
tickFormatter={(value) => `${value.toFixed(2)}%`} <CartesianGrid strokeDasharray="3 3" />
/> <XAxis dataKey="year" />
<YAxis <YAxis
yAxisId="right" yAxisId="left"
orientation="right" tickFormatter={(value) => `${value.toFixed(2)}%`}
tickFormatter={(value) => `${value.toFixed(2)}`} />
/> <YAxis
<Tooltip yAxisId="right"
formatter={(value: number, name: string) => { orientation="right"
if (name === 'Performance') return [`${value.toFixed(2)}%`, name]; tickFormatter={(value) => `${value.toFixed(2)}`}
return [`${value.toFixed(2)}`, name]; />
}} <Tooltip
labelFormatter={(year) => `Year ${year}`} formatter={(value: number, name: string) => {
/> if (name === 'Performance') return [`${value.toFixed(2)}%`, name];
<Line return [`${value.toFixed(2)}`, name];
type="monotone" }}
dataKey="percentage" labelFormatter={(year) => `Year ${year}`}
name="Performance" />
stroke="url(#colorGradient)" <Line
dot={<CustomizedDot />} type="monotone"
strokeWidth={2} dataKey="percentage"
yAxisId="left" name="Performance"
/> stroke="url(#colorGradient)"
<Line dot={<CustomizedDot />}
type="monotone" strokeWidth={2}
dataKey="price" yAxisId="left"
name="Price" />
stroke="#666" <Line
strokeDasharray="5 5" type="monotone"
dot={false} dataKey="price"
yAxisId="right" name="Price"
/> stroke="#666"
<defs> strokeDasharray="5 5"
<linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1"> dot={false}
<stop offset="0%" stopColor="#22c55e" /> yAxisId="right"
<stop offset="100%" stopColor="#ef4444" /> />
</linearGradient> <defs>
</defs> <linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
</LineChart> <stop offset="0%" stopColor="#22c55e" />
</ResponsiveContainer> <stop offset="100%" stopColor="#ef4444" />
</div> </linearGradient>
<div className="mt-6 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4"> </defs>
{sortedPerformances.map(({ year, percentage, price }) => ( </LineChart>
<div </ResponsiveContainer>
key={year} </div>
className={`p-3 rounded-lg ${
percentage >= 0 ? 'bg-green-100 dark:bg-green-900/30' : 'bg-red-100 dark:bg-red-900/30' {/* Performance Cards */}
}`} <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 dark:text-gray-300">
> {sortedPerformances.map(({ year, percentage, price }) => (
<div className="text-sm font-medium">{year}</div> <div
<div className={`text-lg font-bold ${ key={year}
percentage >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' className={`p-3 rounded-lg ${
}`}> percentage >= 0 ? 'bg-green-100 dark:bg-green-900/30' : 'bg-red-100 dark:bg-red-900/30'
{percentage.toFixed(2)}% }`}
</div> >
{price && ( <div className="text-sm font-medium">{year}</div>
<div className="text-sm text-gray-500 dark:text-gray-400"> <div className={`text-lg font-bold ${
{price.toFixed(2)} percentage >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'
}`}>
{percentage.toFixed(2)}%
</div> </div>
)} {price && (
</div> <div className="text-sm text-gray-500 dark:text-gray-400">
))} {price.toFixed(2)}
</div>
)}
</div>
))}
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
import { X } from "lucide-react"; import { X } from "lucide-react";
import { memo } from "react"; import { memo, useEffect } from "react";
import { import {
CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis
} from "recharts"; } from "recharts";
@ -11,6 +11,15 @@ interface PortfolioPerformanceModalProps {
export const PortfolioPerformanceModal = memo(({ performances, onClose }: PortfolioPerformanceModalProps) => { export const PortfolioPerformanceModal = memo(({ performances, onClose }: PortfolioPerformanceModalProps) => {
const sortedPerformances = [...performances].sort((a, b) => a.year - b.year); const sortedPerformances = [...performances].sort((a, b) => a.year - b.year);
// Prevent body scroll when modal is open
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = 'unset';
};
}, []);
const CustomizedDot = (props: any) => { const CustomizedDot = (props: any) => {
const { cx, cy, payload } = props; const { cx, cy, payload } = props;
return ( return (
@ -24,69 +33,80 @@ export const PortfolioPerformanceModal = memo(({ performances, onClose }: Portfo
}; };
return ( return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/50 z-50 md:flex md:items-center md:justify-center">
<div className="bg-white dark:bg-slate-800 p-6 rounded-lg w-[80vw] max-w-4xl"> <div className="h-full md:h-auto w-full md:w-[80vw] bg-white dark:bg-slate-800 md:rounded-lg overflow-hidden">
<div className="flex justify-between items-center mb-6"> {/* Header - Fixed */}
<div className="sticky top-0 z-10 bg-white dark:bg-slate-800 p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
<h2 className="text-xl font-bold dark:text-gray-300">Portfolio Performance History</h2> <h2 className="text-xl font-bold dark:text-gray-300">Portfolio Performance History</h2>
<button onClick={onClose} className="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded"> <button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded"
>
<X className="w-6 h-6 dark:text-gray-300" /> <X className="w-6 h-6 dark:text-gray-300" />
</button> </button>
</div> </div>
<div className="h-[400px]">
<ResponsiveContainer> {/* Content - Scrollable */}
<LineChart data={sortedPerformances}> <div className="overflow-y-auto h-[calc(100vh-64px)] md:h-[calc(80vh-64px)] p-6">
<CartesianGrid strokeDasharray="3 3" /> {/* Chart */}
<XAxis dataKey="year" /> <div className="h-[400px] mb-6">
<YAxis <ResponsiveContainer>
yAxisId="left" <LineChart data={sortedPerformances}>
tickFormatter={(value) => `${value.toFixed(2)}%`} <CartesianGrid strokeDasharray="3 3" />
/> <XAxis dataKey="year" />
<YAxis <YAxis
yAxisId="right" yAxisId="left"
orientation="right" tickFormatter={(value) => `${value.toFixed(2)}%`}
tickFormatter={(value) => `${value.toLocaleString()}`} />
/> <YAxis
<Tooltip yAxisId="right"
formatter={(value: number, name: string) => { orientation="right"
if (name === 'Performance') return [`${value.toFixed(2)}%`, name]; tickFormatter={(value) => `${value.toLocaleString()}`}
return [`${value.toLocaleString()}`, 'Portfolio Value']; />
}} <Tooltip
labelFormatter={(year) => `Year ${year}`} formatter={(value: number, name: string) => {
/> if (name === 'Performance') return [`${value.toFixed(2)}%`, name];
<Line return [`${value.toLocaleString()}`, 'Portfolio Value'];
type="monotone" }}
dataKey="percentage" labelFormatter={(year) => `Year ${year}`}
name="Performance" />
stroke="url(#colorGradient)" <Line
dot={<CustomizedDot />} type="monotone"
strokeWidth={2} dataKey="percentage"
yAxisId="left" name="Performance"
/> stroke="url(#colorGradient)"
<defs> dot={<CustomizedDot />}
<linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1"> strokeWidth={2}
<stop offset="0%" stopColor="#22c55e" /> yAxisId="left"
<stop offset="100%" stopColor="#ef4444" /> />
</linearGradient> <defs>
</defs> <linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
</LineChart> <stop offset="0%" stopColor="#22c55e" />
</ResponsiveContainer> <stop offset="100%" stopColor="#ef4444" />
</div> </linearGradient>
<div className="mt-6 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 dark:text-gray-300"> </defs>
{sortedPerformances.map(({ year, percentage }) => ( </LineChart>
<div </ResponsiveContainer>
key={year} </div>
className={`p-3 rounded-lg ${
percentage >= 0 ? 'bg-green-100 dark:bg-green-900/30' : 'bg-red-100 dark:bg-red-900/30' {/* Performance Cards */}
}`} <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 dark:text-gray-300">
> {sortedPerformances.map(({ year, percentage }) => (
<div className="text-sm font-medium">{year}</div> <div
<div className={`text-lg font-bold ${ key={year}
percentage >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' className={`p-3 rounded-lg ${
}`}> percentage >= 0 ? 'bg-green-100 dark:bg-green-900/30' : 'bg-red-100 dark:bg-red-900/30'
{percentage.toFixed(2)}% }`}
>
<div className="text-sm font-medium">{year}</div>
<div className={`text-lg font-bold ${
percentage >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'
}`}>
{percentage.toFixed(2)}%
</div>
</div> </div>
</div> ))}
))} </div>
</div> </div>
</div> </div>
</div> </div>