361 lines
13 KiB
TypeScript
361 lines
13 KiB
TypeScript
import React from "react";
|
|
import * as z from "zod";
|
|
import { ImageSchema, IconSchema } from "../defaultSchemes";
|
|
import {
|
|
ChartContainer,
|
|
ChartTooltip,
|
|
ChartTooltipContent,
|
|
} from "@/components/ui/chart";
|
|
import {
|
|
BarChart,
|
|
Bar,
|
|
AreaChart,
|
|
Area,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
} from "recharts";
|
|
|
|
export const layoutName = "Statistic Dual Chart Slide";
|
|
export const layoutId = "statistic-dual-chart-slide";
|
|
export const layoutDescription = "A slide with a statistic and a chart";
|
|
|
|
// Schema definition
|
|
export const Schema = z.object({
|
|
sectionTitle: z.string().min(3).max(30).default("PERFORMANCE METRICS").meta({
|
|
description:
|
|
"Main section heading - adapt to presentation topic (e.g., 'Climate Analysis', 'Health Outcomes', 'Research Data', 'Impact Assessment')",
|
|
}),
|
|
|
|
organizationName: z
|
|
.string()
|
|
.min(2)
|
|
.max(30)
|
|
.default("Your Organization")
|
|
.meta({
|
|
description: "Name of the organization or entity presenting the data",
|
|
}),
|
|
|
|
brandLogo: ImageSchema.default({
|
|
__image_url__: "https://via.placeholder.com/40x40/22C55E/FFFFFF?text=L",
|
|
__image_prompt__:
|
|
"Professional organization logo - clean and modern design",
|
|
}).meta({
|
|
description: "Logo or brand mark representing the organization",
|
|
}),
|
|
|
|
barChartData: z
|
|
.array(
|
|
z.object({
|
|
name: z.string(),
|
|
series1: z.number(),
|
|
series2: z.number(),
|
|
series3: z.number(),
|
|
})
|
|
)
|
|
.min(5)
|
|
.max(5)
|
|
.default([
|
|
{ name: "Item 1", series1: 5, series2: 5, series3: 8 },
|
|
{ name: "Item 2", series1: 8, series2: 8, series3: 15 },
|
|
{ name: "Item 3", series1: 15, series2: 10, series3: 18 },
|
|
{ name: "Item 4", series1: 18, series2: 14, series3: 22 },
|
|
{ name: "Item 5", series1: 22, series2: 20, series3: 8 },
|
|
])
|
|
.meta({
|
|
description:
|
|
"CRITICAL: Provide topic-specific data for the left bar chart. For global warming: 5 years of data (2020-2024) with CO2 emissions by sector (Transport, Industry, Energy) with actual values. For healthcare: Patient outcomes across 5 categories (Prevention, Treatment, Recovery) with real percentages. For education: Student performance across 5 metrics (Reading, Math, Science) with grade levels. Use realistic data patterns and values.",
|
|
}),
|
|
|
|
areaChartData: z
|
|
.array(
|
|
z.object({
|
|
name: z.string(),
|
|
series1: z.number(),
|
|
series2: z.number(),
|
|
series3: z.number(),
|
|
})
|
|
)
|
|
.min(5)
|
|
.max(5)
|
|
.default([
|
|
{ name: "Item 1", series1: 20, series2: 30, series3: 15 },
|
|
{ name: "Item 2", series1: 40, series2: 45, series3: 35 },
|
|
{ name: "Item 3", series1: 45, series2: 50, series3: 80 },
|
|
{ name: "Item 4", series1: 50, series2: 45, series3: 85 },
|
|
{ name: "Item 5", series1: 80, series2: 75, series3: 120 },
|
|
])
|
|
.meta({
|
|
description:
|
|
"CRITICAL: Provide topic-specific data for the right area chart. For global warming: Cumulative data over 5 time periods showing renewable energy adoption, carbon reduction efforts, and policy implementations with realistic growth trends. For healthcare: Cumulative patient care metrics showing improvement over time. For education: Progressive learning outcomes showing student advancement. Ensure data shows meaningful trends relevant to the topic.",
|
|
}),
|
|
|
|
leftChartTitle: z
|
|
.string()
|
|
.min(5)
|
|
.max(40)
|
|
.default("Our Customer's Satisfaction")
|
|
.meta({
|
|
description:
|
|
"IMPORTANT: Provide topic-specific title for left chart. For global warming: 'Global CO2 Emissions by Sector', 'Temperature Rise by Region', 'Renewable Energy Adoption'. For healthcare: 'Patient Treatment Outcomes', 'Healthcare Quality Metrics', 'Recovery Success Rates'. For education: 'Student Performance by Subject', 'Learning Progress Assessment', 'Academic Achievement Trends'.",
|
|
}),
|
|
|
|
leftChartDescription: z
|
|
.string()
|
|
.min(20)
|
|
.max(200)
|
|
.default(
|
|
"An impressive client satisfaction rate underscores our unwavering commitment to delivering exceptional service and exceeding expectations."
|
|
)
|
|
.meta({
|
|
description:
|
|
"ESSENTIAL: Provide topic-relevant description explaining the left chart data. For global warming: Explain emission sources, trends, and implications. For healthcare: Describe treatment effectiveness and patient outcomes. For education: Explain performance metrics and learning indicators. Make it informative and specific to the data shown.",
|
|
}),
|
|
|
|
rightChartTitle: z.string().min(5).max(40).default("Repeat Order Rate").meta({
|
|
description:
|
|
"IMPORTANT: Provide topic-specific title for right chart. For global warming: 'Climate Action Progress', 'Carbon Reduction Timeline', 'Sustainability Milestones'. For healthcare: 'Patient Recovery Timeline', 'Treatment Progress Tracking', 'Health Improvement Trajectory'. For education: 'Learning Progress Over Time', 'Student Development Path', 'Academic Growth Timeline'.",
|
|
}),
|
|
|
|
rightChartDescription: z
|
|
.string()
|
|
.min(20)
|
|
.max(200)
|
|
.default(
|
|
"Our remarkable client repeat order rate of 123 times are testament to the quality of our products/services and the trust our clients place in our ability."
|
|
)
|
|
.meta({
|
|
description:
|
|
"ESSENTIAL: Provide topic-relevant description explaining the right chart's cumulative/timeline data. For global warming: Describe progress in climate action, policy impact, or environmental improvements. For healthcare: Explain patient journey and recovery progression. For education: Describe learning advancement and skill development over time. Make it specific and data-driven.",
|
|
}),
|
|
});
|
|
|
|
// Chart configuration
|
|
const chartConfig = {
|
|
series1: {
|
|
label: "Series 1",
|
|
color: "#1D9A8A",
|
|
},
|
|
series2: {
|
|
label: "Series 2",
|
|
color: "#A8C97F",
|
|
},
|
|
series3: {
|
|
label: "Series 3",
|
|
color: "#E8F4B8",
|
|
},
|
|
};
|
|
|
|
// Type inference
|
|
type SchemaType = z.infer<typeof Schema>;
|
|
|
|
// Component definition
|
|
const StatisticDualChartSlide = ({ data }: { data: Partial<SchemaType> }) => {
|
|
const {
|
|
sectionTitle,
|
|
organizationName,
|
|
brandLogo,
|
|
barChartData,
|
|
areaChartData,
|
|
leftChartTitle,
|
|
leftChartDescription,
|
|
rightChartTitle,
|
|
rightChartDescription,
|
|
} = data;
|
|
|
|
return (
|
|
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
|
|
{/* Header Section */}
|
|
<div className="h-20 bg-teal-600 px-16 py-4 flex justify-between items-center">
|
|
{/* Title */}
|
|
{sectionTitle && (
|
|
<h1 className="text-4xl font-black text-white">{sectionTitle}</h1>
|
|
)}
|
|
|
|
{/* Company Branding */}
|
|
<div className="flex items-center space-x-3">
|
|
{brandLogo?.__image_url__ && (
|
|
<div className="w-8 h-8">
|
|
<img
|
|
src={brandLogo.__image_url__}
|
|
alt={brandLogo.__image_prompt__}
|
|
className="w-full h-full object-contain"
|
|
/>
|
|
</div>
|
|
)}
|
|
{organizationName && (
|
|
<span className="text-lg font-bold text-white">
|
|
{organizationName}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content Section */}
|
|
<div className="flex-1 h-[calc(100%-80px)] flex">
|
|
{/* Left Chart Section */}
|
|
<div className="w-1/2 p-8 bg-gray-50 flex flex-col">
|
|
{/* Chart Legend */}
|
|
<div className="flex items-center justify-start mb-4 space-x-4">
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-teal-600 rounded-full"></div>
|
|
<span className="text-sm text-gray-600">Series 1</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-green-400 rounded-full"></div>
|
|
<span className="text-sm text-gray-600">Series 2</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-yellow-200 rounded-full"></div>
|
|
<span className="text-sm text-gray-600">Series 3</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bar Chart */}
|
|
{barChartData && barChartData.length > 0 && (
|
|
<div className="flex-1 mb-6">
|
|
<ChartContainer config={chartConfig} className="h-full w-full">
|
|
<BarChart
|
|
data={barChartData}
|
|
margin={{ top: 10, right: 20, left: 0, bottom: 30 }}
|
|
barCategoryGap="20%"
|
|
>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
|
<XAxis
|
|
dataKey="name"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: "#666" }}
|
|
/>
|
|
<YAxis
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: "#666" }}
|
|
/>
|
|
<ChartTooltip content={<ChartTooltipContent />} />
|
|
<Bar
|
|
dataKey="series1"
|
|
fill="#1D9A8A"
|
|
radius={[2, 2, 0, 0]}
|
|
barSize={15}
|
|
/>
|
|
<Bar
|
|
dataKey="series2"
|
|
fill="#A8C97F"
|
|
radius={[2, 2, 0, 0]}
|
|
barSize={15}
|
|
/>
|
|
<Bar
|
|
dataKey="series3"
|
|
fill="#E8F4B8"
|
|
radius={[2, 2, 0, 0]}
|
|
barSize={15}
|
|
/>
|
|
</BarChart>
|
|
</ChartContainer>
|
|
</div>
|
|
)}
|
|
|
|
{/* Chart Description */}
|
|
<div className="space-y-3">
|
|
{leftChartTitle && (
|
|
<h3 className="text-xl font-bold text-gray-900">
|
|
{leftChartTitle}
|
|
</h3>
|
|
)}
|
|
{leftChartDescription && (
|
|
<p className="text-base leading-relaxed text-gray-700">
|
|
{leftChartDescription}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Chart Section */}
|
|
<div className="w-1/2 p-8 bg-white flex flex-col">
|
|
{/* Chart Legend */}
|
|
<div className="flex items-center justify-end mb-4 space-x-4">
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-yellow-200 rounded-full"></div>
|
|
<span className="text-sm text-gray-600">Series 1</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-green-400 rounded-full"></div>
|
|
<span className="text-sm text-gray-600">Series 2</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-3 h-3 bg-teal-600 rounded-full"></div>
|
|
<span className="text-sm text-gray-600">Series 3</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Area Chart */}
|
|
{areaChartData && areaChartData.length > 0 && (
|
|
<div className="flex-1 mb-6">
|
|
<ChartContainer config={chartConfig} className="h-full w-full">
|
|
<AreaChart
|
|
data={areaChartData}
|
|
margin={{ top: 10, right: 20, left: 0, bottom: 30 }}
|
|
>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
|
<XAxis
|
|
dataKey="name"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: "#666" }}
|
|
/>
|
|
<YAxis
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: "#666" }}
|
|
/>
|
|
<ChartTooltip content={<ChartTooltipContent />} />
|
|
<Area
|
|
type="monotone"
|
|
dataKey="series3"
|
|
stackId="1"
|
|
stroke="#1D9A8A"
|
|
fill="#1D9A8A"
|
|
fillOpacity={0.8}
|
|
/>
|
|
<Area
|
|
type="monotone"
|
|
dataKey="series2"
|
|
stackId="1"
|
|
stroke="#A8C97F"
|
|
fill="#A8C97F"
|
|
fillOpacity={0.8}
|
|
/>
|
|
<Area
|
|
type="monotone"
|
|
dataKey="series1"
|
|
stackId="1"
|
|
stroke="#E8F4B8"
|
|
fill="#E8F4B8"
|
|
fillOpacity={0.8}
|
|
/>
|
|
</AreaChart>
|
|
</ChartContainer>
|
|
</div>
|
|
)}
|
|
|
|
{/* Chart Description */}
|
|
<div className="space-y-3">
|
|
{rightChartTitle && (
|
|
<h3 className="text-xl font-bold text-gray-900">
|
|
{rightChartTitle}
|
|
</h3>
|
|
)}
|
|
{rightChartDescription && (
|
|
<p className="text-base leading-relaxed text-gray-700">
|
|
{rightChartDescription}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StatisticDualChartSlide;
|