feat: Report template added

This commit is contained in:
shiva raj badu 2026-04-07 12:58:18 +05:45
parent 604bb95386
commit 8f0b3b9e85
No known key found for this signature in database
16 changed files with 2270 additions and 0 deletions

View file

@ -0,0 +1,133 @@
import * as z from "zod";
import { WorkflowBarChart } from "./chartPrimitives";
const InsightItemSchema = z.object({
title: z.string().min(3).max(18).meta({
description: "Short insight title shown next to the icon.",
}),
description: z.string().min(20).max(84).meta({
description: "Supporting text shown below the insight title.",
}),
});
const ChartPointSchema = z.object({
label: z.string().min(1).max(12).meta({
description: "Chart axis label.",
}),
value: z.number().min(0).max(1000).meta({
description: "Bar chart value.",
}),
});
export const slideLayoutId = "data-analysis-bar-slide";
export const slideLayoutName = "Data Analysis Bar Slide";
export const slideLayoutDescription =
"A slide with a title at the top, a vertical list of three analysis points on the left, and a bar chart on the right. Each analysis point contains a small icon badge, a short title, and a supporting description.";
export const Schema = z.object({
title: z.string().min(3).max(28).default("Data Analysis").meta({
description: "Slide title shown at the top-left.",
}),
itemIcon: z.object({
__icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"),
__icon_query__: z.string().default("pulse icon"),
}).default({
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
}).meta({
description: "Icon shown in each analysis item badge.",
}),
items: z
.array(InsightItemSchema)
.min(3)
.max(3)
.default([
{ title: "Title 1", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 2", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 2", description: "Ut enim ad minima veniam, quis." },
])
.meta({
description: "Three analysis points shown in the left column.",
}),
chartData: z
.array(ChartPointSchema)
.min(7)
.max(7)
.default([
{ label: "Mon", value: 120 },
{ label: "Tue", value: 200 },
{ label: "Wed", value: 150 },
{ label: "Thu", value: 80 },
{ label: "Fri", value: 70 },
{ label: "Sat", value: 110 },
{ label: "Sun", value: 130 },
])
.meta({
description: "Weekly values shown in the bar chart.",
}),
legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({
description: "Legend label shown below the chart.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
const DataAnalysisBarSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, itemIcon, items, chartData, legendLabel } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[64px] pt-[48px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="flex justify-between px-[85px] pt-[44px]">
<div className="space-y-[38px] pt-[8px]">
{items?.map((item, index) => (
<div key={`${item.title}-${index}`}>
<div className="flex items-center gap-[14px]">
<div className="flex h-[55px] w-[55px] items-center justify-center rounded-full bg-[#4d4ef3] text-white">
<img
src={itemIcon?.__icon_url__}
alt={itemIcon?.__icon_query__}
className="h-[25px] w-[25px] object-contain"
style={{ filter: "brightness(0) invert(1)" }}
/>
</div>
<h3 className="text-[20px] font-medium tracking-[2.074px] text-[#232223]">
{item.title}
</h3>
</div>
<p className="mt-[20px] text-[24px] leading-[26.667px] text-[#232223]">
{item.description}
</p>
</div>
))}
</div>
<div className="ml-[44px] flex flex-col items-center">
<div className="h-[346px] w-[560px]">
<WorkflowBarChart data={chartData ?? []} />
</div>
<div className="mt-[12px] flex items-center gap-[10px] text-[24px] tracking-[-0.03em] text-[#4d4ef3]">
<span className="h-[12px] w-[12px] rounded-full bg-[#4d4ef3]" />
<p>{legendLabel}</p>
</div>
</div>
</div>
</div>
);
};
export default DataAnalysisBarSlide;

View file

@ -0,0 +1,350 @@
import type { ReactNode } from "react";
import * as z from "zod";
import {
AreaTrendChart,
CompactBarChart,
CompactPieChart,
SemiDonutChart,
TrendLineChart,
} from "./chartPrimitives";
const SummaryCardSchema = z.object({
value: z.string().min(1).max(8).meta({
description: "Primary metric value shown in the compact summary card.",
}),
label: z.string().min(3).max(20).meta({
description: "Short summary card label.",
}),
});
const ChartPointSchema = z.object({
label: z.string().min(1).max(12).meta({
description: "Chart axis label.",
}),
value: z.number().min(0).max(1000).meta({
description: "Single-series chart value.",
}),
});
const DualChartPointSchema = z.object({
label: z.string().min(1).max(12).meta({
description: "Chart axis label.",
}),
valueA: z.number().min(0).max(1000).meta({
description: "First series value.",
}),
valueB: z.number().min(0).max(1000).meta({
description: "Second series value.",
}),
});
const PieSegmentSchema = z.object({
name: z.string().min(1).max(18).meta({
description: "Category name shown in chart legends.",
}),
value: z.number().min(1).max(1000).meta({
description: "Category value used in the chart.",
}),
});
export const slideLayoutId = "data-analysis-dashboard-slide";
export const slideLayoutName = "Data Analysis Dashboard Slide";
export const slideLayoutDescription =
"A dashboard-style slide with a title at the top, a row of compact summary cards underneath, and two stacked dashboard panels below. Each panel is split into three chart cells, creating a six-chart overview made of bar, donut, line, area, pie, and comparison charts.";
export const Schema = z.object({
title: z.string().min(3).max(28).default("Data Analysis").meta({
description: "Slide title shown at the top-left.",
}),
summaryIcon: z.object({
__icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"),
__icon_query__: z.string().default("pulse icon"),
}).default({
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
}).meta({
description: "Icon shown in each compact summary card.",
}),
summaryCards: z
.array(SummaryCardSchema)
.min(4)
.max(4)
.default([
{ value: "5", label: "Text 1" },
{ value: "52", label: "Text 2" },
{ value: "4", label: "Text 3" },
{ value: "80%", label: "Text 4" },
])
.meta({
description: "Four compact summary cards displayed above the dashboard panels.",
}),
workflowBars: z
.array(ChartPointSchema)
.min(7)
.max(7)
.default([
{ label: "Mon", value: 120 },
{ label: "Tue", value: 200 },
{ label: "Wed", value: 150 },
{ label: "Thu", value: 80 },
{ label: "Fri", value: 70 },
{ label: "Sat", value: 110 },
{ label: "Sun", value: 130 },
])
.meta({
description: "Bar chart data shown in the top-left dashboard cell.",
}),
gaugeSegments: z
.array(PieSegmentSchema)
.min(3)
.max(3)
.default([
{ name: "Category A", value: 45 },
{ name: "Category B", value: 30 },
{ name: "Category C", value: 25 },
])
.meta({
description: "Three segments used in the top-center semi-donut chart.",
}),
trendSeries: z
.array(DualChartPointSchema)
.min(7)
.max(7)
.default([
{ label: "Label", valueA: 22, valueB: 35 },
{ label: "Label", valueA: 54, valueB: 26 },
{ label: "Label", valueA: 44, valueB: 70 },
{ label: "Label", valueA: 78, valueB: 52 },
{ label: "Label", valueA: 50, valueB: 44 },
{ label: "Label", valueA: 32, valueB: 60 },
{ label: "Label", valueA: 58, valueB: 40 },
])
.meta({
description: "Two-series line chart data shown in the top-right cell.",
}),
detailedArea: z
.array(ChartPointSchema)
.min(7)
.max(7)
.default([
{ label: "12:00", value: 22 },
{ label: "13:00", value: 64 },
{ label: "14:00", value: 48 },
{ label: "15:00", value: 56 },
{ label: "16:00", value: 41 },
{ label: "17:00", value: 58 },
{ label: "18:00", value: 63 },
])
.meta({
description: "Area chart data shown in the bottom-left dashboard cell.",
}),
shareBreakdown: z
.array(PieSegmentSchema)
.min(3)
.max(3)
.default([
{ name: "Category A", value: 50 },
{ name: "Category B", value: 30 },
{ name: "Category C", value: 20 },
])
.meta({
description: "Pie chart data shown in the bottom-center dashboard cell.",
}),
comparisonBars: z
.array(ChartPointSchema)
.min(7)
.max(7)
.default([
{ label: "Jan", value: 70 },
{ label: "Feb", value: 170 },
{ label: "Mar", value: 110 },
{ label: "Apr", value: 42 },
{ label: "May", value: 88 },
{ label: "Jun", value: 106 },
{ label: "Jul", value: 112 },
])
.meta({
description: "Bar chart data shown in the bottom-right dashboard cell.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
function SummaryCard({
value,
label,
iconUrl,
iconAlt,
}: {
value: string;
label: string;
iconUrl?: string;
iconAlt?: string;
}) {
return (
<div className="flex h-[74px] items-center rounded-[14px] bg-white px-[16px]">
<div className="flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-[#4d4ef3] text-white">
<img
src={iconUrl ?? ""}
alt={iconAlt ?? ""}
className="h-[10px] w-[10px] object-contain"
style={{ filter: "brightness(0) invert(1)" }}
/>
</div>
<div className="ml-[10px]">
<p className="text-[22px] leading-none tracking-[-0.04em] text-[#232223]">
{value}
</p>
<p className="mt-[4px] text-[12px] leading-none text-[#535665]">{label}</p>
</div>
</div>
);
}
function ChartCell({
children,
footer,
topLegend,
}: {
children: ReactNode;
footer?: ReactNode;
topLegend?: ReactNode;
}) {
return (
<div className="flex h-full flex-col px-[10px] py-[10px]">
{topLegend && <div className="mb-[4px] flex justify-center">{topLegend}</div>}
<div className="min-h-0 flex-1">{children}</div>
{footer && <div className="mt-[4px] flex justify-center">{footer}</div>}
</div>
);
}
function DotLegend({
items,
}: {
items: { label: string; color: string }[];
}) {
return (
<div className="flex flex-wrap items-center justify-center gap-[10px] text-[8px] text-[#6b7280]">
{items.map((item) => (
<span key={item.label} className="flex items-center gap-[4px]">
<span
className="block h-[6px] w-[6px] rounded-full"
style={{ backgroundColor: item.color }}
/>
{item.label}
</span>
))}
</div>
);
}
const DataAnalysisDashboardSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, summaryIcon, summaryCards, workflowBars, gaugeSegments, trendSeries, detailedArea, shareBreakdown, comparisonBars } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#4d4ef3]"
style={{ height: 188 }}
/>
<div className="px-[74px] pt-[44px]">
<h2 className="text-[78px] font-semibold leading-none tracking-[-0.06em] text-[#232223]">
{title}
</h2>
</div>
<div className="grid grid-cols-4 gap-[16px] px-[74px] pt-[14px]">
{summaryCards?.map((card, index) => (
<SummaryCard
key={`${card.label}-${index}`}
value={card.value}
label={card.label}
iconUrl={summaryIcon?.__icon_url__}
iconAlt={summaryIcon?.__icon_query__}
/>
))}
</div>
<div className="flex flex-col gap-[12px] px-[74px] pt-[12px]">
<div className="grid h-[168px] grid-cols-3 divide-x divide-[#ecf0f6] rounded-[16px] bg-white">
<ChartCell
footer={
<DotLegend items={[{ label: "Traditional Workflow", color: "#4d4ef3" }]} />
}
>
<CompactBarChart data={workflowBars ?? []} />
</ChartCell>
<ChartCell
footer={
<DotLegend
items={[
{ label: "Category A", color: "#4d4ef3" },
{ label: "Category B", color: "#9fb6ff" },
{ label: "Category C", color: "#e8eefb" },
]}
/>
}
>
<SemiDonutChart data={gaugeSegments ?? []} />
</ChartCell>
<ChartCell
topLegend={
<div className="flex gap-[10px] text-[8px] text-[#6b7280]">
<p>Category A</p>
<p>Category B</p>
</div>
}
>
<TrendLineChart data={trendSeries ?? []} />
</ChartCell>
</div>
<div className="grid h-[168px] grid-cols-3 divide-x divide-[#ecf0f6] rounded-[16px] bg-white">
<ChartCell
footer={
<DotLegend items={[{ label: "Detailed Workflow", color: "#4d4ef3" }]} />
}
>
<AreaTrendChart data={detailedArea ?? []} idPrefix="dashboard-area" />
</ChartCell>
<ChartCell
footer={
<DotLegend
items={[
{ label: "Category A", color: "#4d4ef3" },
{ label: "Category B", color: "#9fb6ff" },
{ label: "Category C", color: "#d7dff4" },
]}
/>
}
>
<CompactPieChart data={shareBreakdown ?? []} />
</ChartCell>
<ChartCell
footer={
<DotLegend
items={[
{ label: "Category A", color: "#4d4ef3" },
{ label: "Category B", color: "#9fb6ff" },
]}
/>
}
>
<CompactBarChart data={comparisonBars ?? []} />
</ChartCell>
</div>
</div>
</div>
);
};
export default DataAnalysisDashboardSlide;

View file

@ -0,0 +1,114 @@
import * as z from "zod";
import { WorkflowBarChart } from "./chartPrimitives";
const ChartPointSchema = z.object({
label: z.string().min(1).max(12).meta({
description: "Chart axis label.",
}),
value: z.number().min(0).max(1000).meta({
description: "Bar chart value.",
}),
});
export const slideLayoutId = "data-analysis-insight-bar-slide";
export const slideLayoutName = "Data Analysis Insight Bar Slide";
export const slideLayoutDescription =
"A slide with a title at the top, a single featured insight block on the left containing an icon badge and a paragraph, and a bar chart on the right with a legend below it.";
export const Schema = z.object({
title: z.string().min(3).max(28).default("Data Analysis").meta({
description: "Slide title shown at the top-left.",
}),
insightIcon: z.object({
__icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"),
__icon_query__: z.string().default("pulse icon"),
}).default({
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
}).meta({
description: "Icon shown in the featured insight badge.",
}),
insightBody: z.string().min(80).max(320).default(
"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut alut enim ad minima veniam, quis. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut alut enim ad minima veniam, quis"
).meta({
description: "Featured insight paragraph shown in the left content area.",
}),
chartData: z
.array(ChartPointSchema)
.min(7)
.max(7)
.default([
{ label: "Mon", value: 120 },
{ label: "Tue", value: 200 },
{ label: "Wed", value: 150 },
{ label: "Thu", value: 80 },
{ label: "Fri", value: 70 },
{ label: "Sat", value: 110 },
{ label: "Sun", value: 130 },
])
.meta({
description: "Weekly values shown in the right-side bar chart.",
}),
legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({
description: "Legend label shown below the chart.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
const DataAnalysisInsightBarSlide = ({
data,
}: {
data: Partial<SchemaType>;
}) => {
const { title, insightIcon, insightBody, chartData, legendLabel } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[64px] pt-[48px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="flex justify-between px-[74px] pt-[96px]">
<div className="w-[380px] pt-[24px]">
<div className="flex items-center gap-[14px]">
<div className="flex h-[55px] w-[55px] items-center justify-center rounded-full bg-[#157CFF] text-white">
<img
src={insightIcon?.__icon_url__}
alt={insightIcon?.__icon_query__}
className="h-[25px] w-[25px] object-contain"
/>
</div>
</div>
<p className="mt-[20px] text-[24px] leading-[26.667px] text-[#232223]">
{insightBody}
</p>
</div>
<div className="ml-[28px] flex flex-col items-center">
<div className="h-[346px] w-[560px]">
<WorkflowBarChart data={chartData ?? []} />
</div>
<div className="mt-[12px] flex items-center gap-[10px] text-[24px] tracking-[-0.03em] text-[#157CFF]">
<span className="h-[12px] w-[12px] rounded-full bg-[#157CFF]" />
<p>{legendLabel}</p>
</div>
</div>
</div>
</div>
);
};
export default DataAnalysisInsightBarSlide;

View file

@ -0,0 +1,185 @@
import * as z from "zod";
import { DualLineChart } from "./chartPrimitives";
const LinePointSchema = z.object({
label: z.string().min(1).max(12).meta({
description: "Chart axis label.",
}),
valueA: z.number().min(0).max(1000).meta({
description: "First series value.",
}),
valueB: z.number().min(0).max(1000).meta({
description: "Second series value.",
}),
});
const MetricSchema = z.object({
value: z.string().min(1).max(12).meta({
description: "Primary metric value shown in the stat card.",
}),
label: z.string().min(3).max(24).meta({
description: "Metric label shown below the value.",
}),
description: z.string().min(6).max(36).meta({
description: "Supporting description shown below the label.",
}),
});
const StatColumnSchema = z.object({
metrics: z.array(MetricSchema).min(2).max(2).meta({
description: "Two stacked metrics shown in one stat card.",
}),
});
export const slideLayoutId = "data-analysis-line-stats-slide";
export const slideLayoutName = "Data Analysis Line Stats Slide";
export const slideLayoutDescription =
"A slide with a title at the top, a two-series line chart in the left content area, and two tall metric cards arranged side by side on the right. Each metric card contains two stacked metric blocks.";
export const Schema = z.object({
title: z.string().min(3).max(28).default("Data Analysis").meta({
description: "Slide title shown at the top-left.",
}),
seriesALabel: z.string().min(3).max(20).default("Category A").meta({
description: "Legend label for the first line series.",
}),
seriesBLabel: z.string().min(3).max(20).default("Category B").meta({
description: "Legend label for the second line series.",
}),
lineData: z
.array(LinePointSchema)
.min(7)
.max(7)
.default([
{ label: "label", valueA: 24, valueB: 40 },
{ label: "label", valueA: 55, valueB: 72 },
{ label: "label", valueA: 50, valueB: 98 },
{ label: "label", valueA: 97, valueB: 86 },
{ label: "label", valueA: 70, valueB: 52 },
{ label: "label", valueA: 42, valueB: 78 },
{ label: "label", valueA: 63, valueB: 51 },
])
.meta({
description: "Line chart data displayed on the left side of the slide.",
}),
statColumns: z
.array(StatColumnSchema)
.min(2)
.max(2)
.default([
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
])
.meta({
description: "Two stat cards shown on the right side of the slide.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
type StatMetric = {
value: string;
label: string;
description: string;
};
function StatPill({
metrics,
}: {
metrics: StatMetric[];
}) {
return (
<div className=" h-[438px] w-[248px] overflow-hidden rounded-[127px] bg-[#157CFF] px-[28px] py-[74px] text-center text-white">
{metrics.map((metric, index) => (
<>
<div
key={`${metric.value}-${metric.label}-${index}`}
className={``}
>
<p className="text-[55px] font-medium leading-[ 44.353px] tracking-[-1.09px]">
{metric.value}
</p>
<p className="mt-[6px] text-[20px] font-medium leading-none">{metric.label}</p>
<p className=" text-[20px] leading-[1.15] text-white/90">
{metric.description}
</p>
</div>
{index === 0 && <div className="py-[22px]">
<svg xmlns="http://www.w3.org/2000/svg" width="181" height="1" viewBox="0 0 181 1" fill="none">
<path opacity="0.2" d="M0 0.487305H180.122" stroke="white" strokeWidth="0.974913" strokeDasharray="3.9 1.95" />
</svg>
</div>
}
</>
))}
</div>
);
}
const DataAnalysisLineStatsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, seriesALabel, seriesBLabel, lineData, statColumns } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[64px] pt-[48px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="flex justify-between px-[74px] pt-[40px]">
<div className="w-[474px]">
<div className="flex justify-center gap-[26px] text-[14px] text-[#353538]">
<span className="flex items-center gap-[8px]">
<span className="h-[2px] w-[20px] bg-[#9fb6ff]" />
{seriesALabel}
</span>
<span className="flex items-center gap-[8px]">
<span className="h-[2px] w-[20px] bg-[#4d4ef3]" />
{seriesBLabel}
</span>
</div>
<div className="mt-[12px] h-[356px] w-full">
<DualLineChart data={lineData ?? []} />
</div>
<div className="mt-[2px] text-center text-[18px] text-[#4b5563]">
X axis name
</div>
</div>
<div className="ml-[42px] flex gap-[30px]">
{statColumns?.map((column, index) => (
<StatPill key={`line-stat-column-${index}`} metrics={column.metrics} />
))}
</div>
</div>
</div>
);
};
export default DataAnalysisLineStatsSlide;

View file

@ -0,0 +1,93 @@
import * as z from "zod";
const AnalysisItemSchema = z.object({
title: z.string().min(3).max(18).meta({
description: "Short item title displayed next to the icon.",
}),
description: z.string().min(20).max(84).meta({
description: "Supporting sentence shown below the title.",
}),
});
export const slideLayoutId = "data-analysis-list-slide";
export const slideLayoutName = "Data Analysis List Slide";
export const slideLayoutDescription =
"A slide with a title at the top and a two-column list of analysis points underneath. Each point contains a small circular icon badge, a short title on the same row, and a supporting description directly below.";
export const Schema = z.object({
title: z.string().min(3).max(28).default("Data Analysis").meta({
description: "Slide title shown at the top-left.",
}),
itemIcon: z.object({
__icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"),
__icon_query__: z.string().default("pulse icon"),
}).default({
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
}).meta({
description: "Icon shown in each analysis list badge.",
}),
items: z
.array(AnalysisItemSchema)
.min(6)
.max(6)
.default([
{ title: "Title 1", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 3", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 2", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 4", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 2", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 5", description: "Ut enim ad minima veniam, quis." },
])
.meta({
description: "Six analysis items distributed across two columns.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
const DataAnalysisListSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, itemIcon, items } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[58px] pt-[52px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="grid grid-cols-2 gap-x-[112px] gap-y-[52px] px-[82px] pt-[58px]">
{items?.map((item, index) => (
<div key={`${item.title}-${index}`}>
<div className="flex items-center gap-[14px]">
<div className="flex h-[55px] w-[55px] items-center justify-center rounded-full bg-[#157CFF] text-white">
<img
src={itemIcon?.__icon_url__}
alt={itemIcon?.__icon_query__}
className="h-[26px] w-[26px] object-contain"
style={{ filter: "brightness(0) invert(1)" }}
/>
</div>
<h3 className="text-[20px] font-medium tracking-[2.074px] text-[#232223]">
{item.title}
</h3>
</div>
<p className="mt-5 max-w-[420px] text-[24px] leading-[26.667px] text-[#232223]">
{item.description}
</p>
</div>
))}
</div>
</div>
);
};
export default DataAnalysisListSlide;

View file

@ -0,0 +1,42 @@
import * as z from "zod";
export const Schema = z.object({
title: z.string().min(1).default("Company's "),
subtitle: z.string().min(1).default("Report"),
name: z.string().min(1).default("John Doe"),
position: z.string().min(1).default("Company Name | Strategy, Content, growth"),
})
export type SchemaType = z.infer<typeof Schema>;
export const slideLayoutId = "intro-slide";
export const slideLayoutName = "Intro Slide";
export const slideLayoutDescription =
"A report cover slide with decorative corner accents, a centered two-line title section, a divider directly beneath the title, and a presenter information block below the divider containing a name line and a supporting role or company line.";
const IntroSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, subtitle, name, position } = data;
return (
<div className='relative w-[1280px] h-[720px] aspect-video flex flex-col justify-center items-center'>
<svg className="absolute top-0 left-10" xmlns="http://www.w3.org/2000/svg" width="116" height="251" viewBox="0 0 116 251" fill="none">
<path d="M0 0H44.6667V228.333C44.6667 240.668 34.6677 250.667 22.3333 250.667C9.99898 250.667 0 240.668 0 228.333V0Z" fill="#147CFE" />
<path d="M71.3334 0H116V163C116 175.334 106.001 185.333 93.6667 185.333C81.3324 185.333 71.3334 175.334 71.3334 163V0Z" fill="#147CFE" />
</svg>
<svg className="absolute bottom-0 right-10 rotate-180" xmlns="http://www.w3.org/2000/svg" width="116" height="251" viewBox="0 0 116 251" fill="none">
<path d="M0 0H44.6667V228.333C44.6667 240.668 34.6677 250.667 22.3333 250.667C9.99898 250.667 0 240.668 0 228.333V0Z" fill="#147CFE" />
<path d="M71.3334 0H116V163C116 175.334 106.001 185.333 93.6667 185.333C81.3324 185.333 71.3334 175.334 71.3334 163V0Z" fill="#147CFE" />
</svg>
<div>
<h1 className="text-[#232223] text-[133px] italic text-center font-bold capitalize tracking-[-2.8px]">{title}</h1>
<p className="text-[#232223] text-[93px] text-center font-medium capitalize tracking-[-2.8px]">{subtitle}</p>
</div>
<div className="bg-[#CD7721] w-[67px] h-0.5 my-[78px]" />
<div className="text-center">
<h4 className="text-[#232223] text-[40px] pb-4">{name}</h4>
<p className="text-[19px] text-[#232223]">{position}</p>
</div>
</div>
)
}
export default IntroSlide

View file

@ -0,0 +1,89 @@
import * as z from "zod";
export const slideLayoutId = "introduction-image-slide";
export const slideLayoutName = "Introduction Image Slide";
export const slideLayoutDescription =
"A slide with a title at the top-left, a paragraph block beneath the title, a short bulleted list in the lower-left area, and a large supporting image anchored on the right side of the slide.";
export const Schema = z.object({
title: z.string().min(3).max(32).default("Introduction").meta({
description: "Slide title shown at the top-left.",
}),
body: z.string().min(60).max(280).default(
"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut alut enim ad minima veniam, quis. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut alut enim ad minima veniam, quis"
).meta({
description: "Primary paragraph shown under the title.",
}),
bullets: z
.array(z.string().min(20).max(80))
.min(4)
.max(4)
.default([
"Ut enim ad minima veniam, quis nostrum",
"Exercitationem ullam corporis suscipit",
"Ut enim ad minima veniam, quis nostrum",
"exercitationem ullam corporis suscipit",
])
.meta({
description: "Bullet list shown in the lower-left area.",
}),
featureImage: z.object({
__image_url__: z.string(),
__image_prompt__: z.string(),
}).optional().meta({
description: "Large image shown on the right side of the slide or optional.",
}).default({
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Thoughtful woman portrait on a neutral backdrop",
}),
});
export type SchemaType = z.infer<typeof Schema>;
const IntroductionImageSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, body, bullets, featureImage } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[74px] pt-[76px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="flex gap-28 pl-[96px] pt-[30px]">
<div className="flex flex-col">
<p className=" text-[24px] leading-[26.667px] text-[#232223]">
{body}
</p>
<ul className="mt-8 list-disc pl-[28px] text-[24px] leading-[26.667px] text-[#232223]">
{bullets?.map((bullet, index) => (
<li key={`${bullet}-${index}`} className="mt-[8px]">
{bullet}
</li>
))}
</ul>
</div>
<div className="flex flex-1 items-end justify-end">
<div className="h-[397px] w-[582px] overflow-hidden rounded-l-[106px] bg-[#157CFF]">
<img
src={featureImage?.__image_url__}
alt={featureImage?.__image_prompt__}
className="h-full w-full object-cover"
/>
</div>
</div>
</div>
</div>
);
};
export default IntroductionImageSlide;

View file

@ -0,0 +1,162 @@
import * as z from "zod";
const MetricSchema = z.object({
value: z.string().min(1).max(12).meta({
description: "Primary metric value shown in the card.",
}),
label: z.string().min(3).max(24).meta({
description: "Short metric label shown below the value.",
}),
description: z.string().min(6).max(36).meta({
description: "Supporting text shown below the label.",
}),
});
const StatColumnSchema = z.object({
metrics: z.array(MetricSchema).min(2).max(2).meta({
description: "Two stacked metrics shown in one tall card.",
}),
});
export const slideLayoutId = "introduction-stats-slide";
export const slideLayoutName = "Introduction Stats Slide";
export const slideLayoutDescription =
"A slide with a title and explanatory text on the left, a bulleted list underneath the text, and two tall metric cards placed side by side on the right. Each metric card contains two stacked metric blocks.";
export const Schema = z.object({
title: z.string().min(3).max(32).default("Introduction").meta({
description: "Slide title shown at the top-left.",
}),
body: z.string().min(60).max(320).default(
"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut alut enim ad minima veniam, quis. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut alut enim ad minima veniam, quis"
).meta({
description: "Primary paragraph shown below the title.",
}),
bullets: z
.array(z.string().min(20).max(80))
.min(4)
.max(4)
.default([
"Ut enim ad minima veniam, quis nostrum",
"Exercitationem ullam corporis suscipit",
"Ut enim ad minima veniam, quis nostrum",
"exercitationem ullam corporis suscipit",
])
.meta({
description: "Bullet list shown in the lower-left area.",
}),
statColumns: z
.array(StatColumnSchema)
.min(2)
.max(2)
.default([
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
])
.meta({
description: "Two stat cards shown on the right side of the slide.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
type StatMetric = {
value: string;
label: string;
description: string;
};
function StatPill({
metrics,
}: {
metrics: StatMetric[];
}) {
return (
<div className=" h-[438px] w-[248px] overflow-hidden rounded-[127px] bg-[#157CFF] px-[28px] py-[74px] text-center text-white">
{metrics.map((metric, index) => (
<>
<div
key={`${metric.value}-${metric.label}-${index}`}
className={``}
>
<p className="text-[55px] font-medium leading-[ 44.353px] tracking-[-1.09px]">
{metric.value}
</p>
<p className="mt-[6px] text-[20px] font-medium leading-none">{metric.label}</p>
<p className=" text-[20px] leading-[1.15] text-white/90">
{metric.description}
</p>
</div>
{index === 0 && <div className="py-[22px]">
<svg xmlns="http://www.w3.org/2000/svg" width="181" height="1" viewBox="0 0 181 1" fill="none">
<path opacity="0.2" d="M0 0.487305H180.122" stroke="white" strokeWidth="0.974913" strokeDasharray="3.9 1.95" />
</svg>
</div>
}
</>
))}
</div>
);
}
const IntroductionStatsSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, body, bullets, statColumns } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[64px] pt-[48px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="flex justify-between px-[96px] pt-[38px]">
<div className="">
<p className="max-w-[400px] text-[24px] leading-[26.667px] text-[#232223]">
{body}
</p>
<ul className="mt-[34px] list-disc pl-[28px] text-[24px] leading-[26.667px] text-[#232223]">
{bullets?.map((bullet, index) => (
<li key={`${bullet}-${index}`} className="mt-[8px]">
{bullet}
</li>
))}
</ul>
</div>
<div className="ml-[48px] flex gap-[34px]">
{statColumns?.map((column, index) => (
<StatPill key={`intro-stat-column-${index}`} metrics={column.metrics} />
))}
</div>
</div>
</div>
);
};
export default IntroductionStatsSlide;

View file

@ -0,0 +1,119 @@
import * as z from "zod";
const MilestoneItemSchema = z.object({
stepNumber: z.string().min(2).max(4).meta({
description: "Short milestone number such as 01 or 05.",
}),
heading: z.string().min(3).max(18).meta({
description: "Heading displayed below the milestone marker.",
}),
description: z.string().min(20).max(80).meta({
description: "Supporting milestone description shown under the heading.",
}),
});
export const slideLayoutId = "milestone-slide";
export const slideLayoutName = "Milestone Slide";
export const slideLayoutDescription =
"A slide with a title at the top and a single horizontal milestone sequence below it. The sequence contains five circular markers aligned in one row, and each marker has a heading and description placed directly underneath. The activeIndex field controls which marker is emphasized while the remaining markers stay in the default state.";
export const Schema = z.object({
title: z.string().min(3).max(24).default("Milestone").meta({
description: "Slide title shown at the top-left.",
}),
activeIndex: z.number().int().min(0).max(4).default(4).meta({
description: "Zero-based index of the highlighted milestone.",
}),
items: z
.array(MilestoneItemSchema)
.min(5)
.max(5)
.default([
{
stepNumber: "01",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
stepNumber: "02",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
stepNumber: "03",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
stepNumber: "04",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
stepNumber: "05",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
])
.meta({
description: "Five milestone entries rendered across the slide.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
const MilestoneSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, activeIndex, items } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#F9F8F8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[70px] pt-[56px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="mt-[52px] pl-[74px] pr-[63px]">
<div className="flex items-center justify-center">
{items?.map((item, index) => {
const isActive = index === activeIndex;
return (
<div className=" " key={`${item.stepNumber}-${index}`}>
<div
className={`relative flex h-[270px] w-[270px] items-center justify-center rounded-full ${isActive
? "z-10 bg-[#157CFF] text-white"
: "border border-[#157CFF] bg-white text-[#157CFE]"
} ${index > 0 ? "ml-[-45px]" : ""} `}
>
<span className={`${isActive ? "text-white" : "text-[#157CFF]"} text-[42px] font-medium tracking-[0.18em]`}>
{item.stepNumber}
</span>
</div>
<div key={`${item.heading}-${index}`} className={`text-center mt-[20px] text-[#232223] ${index > 0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`}>
<h3 className="text-[20px] text-[#232223] font-medium tracking-[2.074px]">
{item.heading}
</h3>
<p className="mt-[6px] text-[24px] leading-[26.667px] text-[#232223]">
{item.description}
</p>
</div>
</div>
);
})}
</div>
</div>
</div>
);
};
export default MilestoneSlide;

View file

@ -0,0 +1,142 @@
import * as z from "zod";
const MetricSchema = z.object({
value: z.string().min(1).max(12).meta({
description: "Primary metric value shown in the pill.",
}),
label: z.string().min(3).max(24).meta({
description: "Short label shown below the metric value.",
}),
description: z.string().min(6).max(36).meta({
description: "Supporting metric description shown below the label.",
}),
});
const MetricColumnSchema = z.object({
metrics: z.array(MetricSchema).min(1).max(2).meta({
description: "One or two metrics shown in a single snapshot pill.",
}),
});
export const slideLayoutId = "performance-snapshot-slide";
export const slideLayoutName = "Performance Snapshot Slide";
export const slideLayoutDescription =
"A slide with a title at the top and three tall metric cards arranged horizontally below it. Each card can contain one or two stacked metric blocks, and each block includes a main value, a label, and a supporting description.";
export const Schema = z.object({
title: z.string().min(3).max(40).default("Performance Snapshot").meta({
description: "Slide title shown at the top-left.",
}),
columns: z
.array(MetricColumnSchema)
.min(3)
.max(4)
.default([
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
{
metrics: [
{ value: "25K", label: "Students", description: "Ut enim ad minima" },
],
},
])
.meta({
description: "Three metric columns displayed beneath the title.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
type StatMetric = {
value: string;
label: string;
description: string;
};
function StatPill({
metrics,
}: {
metrics: StatMetric[];
}) {
return (
<div className=" h-[438px] w-[248px] overflow-hidden rounded-[127px] bg-[#157CFF] px-[28px] py-[74px] text-center text-white">
{metrics.map((metric, index) => (
<>
<div
key={`${metric.value}-${metric.label}-${index}`}
className={``}
>
<p className="text-[55px] font-medium leading-[ 44.353px] tracking-[-1.09px]">
{metric.value}
</p>
<p className="mt-[6px] text-[20px] font-medium leading-none">{metric.label}</p>
<p className=" text-[20px] leading-[1.15] text-white/90">
{metric.description}
</p>
</div>
{index === 0 && <div className="py-[22px]">
<svg xmlns="http://www.w3.org/2000/svg" width="181" height="1" viewBox="0 0 181 1" fill="none">
<path opacity="0.2" d="M0 0.487305H180.122" stroke="white" strokeWidth="0.974913" strokeDasharray="3.9 1.95" />
</svg>
</div>
}
</>
))}
</div>
);
}
const PerformanceSnapshotSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, columns } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[64px] pt-[48px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="mt-[44px] flex justify-start gap-[33px] pl-[94px]">
{columns?.map((column, index) => (
<StatPill
key={`snapshot-column-${index}`}
metrics={column.metrics}
/>
))}
</div>
</div>
);
};
export default PerformanceSnapshotSlide;

View file

@ -0,0 +1,209 @@
import * as z from "zod";
import { Fragment } from "react/jsx-runtime";
const ServiceItemSchema = z.object({
icon: z.object({
__icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"),
__icon_query__: z.string().default("pulse icon"),
}).default({
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
}).meta({
description: "Icon used for service circles.",
}),
heading: z.string().min(3).max(18).meta({
description: "Heading shown below the service icon.",
}),
description: z.string().min(20).max(84).meta({
description: "Supporting description below the service heading.",
}),
});
export const slideLayoutId = "services-slide";
export const slideLayoutName = "Services Slide";
export const slideLayoutDescription =
"A slide with a title and a three-step horizontal service flow. Each step contains a circular icon area, a heading, and a description placed underneath. Directional connectors between the circles indicate sequence, and the activeIndex field determines which step is emphasized.";
export const Schema = z.object({
title: z.string().min(3).max(24).default("Services").meta({
description: "Slide title shown at the top-left.",
}),
activeIndex: z.number().int().min(0).max(2).default(2).meta({
description: "Zero-based index of the emphasized service step.",
}),
items: z
.array(ServiceItemSchema)
.min(3)
.max(5)
.default([
{
icon: {
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
},
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
icon: {
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "upload icon",
},
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
icon: {
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
},
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
icon: {
__icon_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg",
__icon_query__: "pulse icon",
},
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
])
.meta({
description: "Three sequential service items displayed on the slide.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
function ServiceGlyph({
iconUrl,
iconAlt,
isActive,
}: {
iconUrl: string;
iconAlt: string;
isActive: boolean;
}) {
return (
<img
src={iconUrl}
alt={iconAlt}
className="h-[62px] w-[62px] object-contain"
style={{ filter: isActive ? "brightness(0) invert(1)" : "none" }}
/>
);
}
const ServicesSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { title, activeIndex, items } = data;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]">
<div
className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="px-[70px] pt-[56px]">
<h2 className="text-[80px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
</div>
<div className="mt-[56px] flex items-start justify-center px-[82px]">
{items?.map((item, index) => {
const isActive = index === activeIndex;
return (
<Fragment key={`${item.heading}-${index}`}>
<div key={`${item.heading}-${index}`} className="flex w-[302px] flex-col items-center text-center">
<div
className={`flex items-center justify-center rounded-full ${isActive
? "bg-[#157CFF] text-white"
: "border border-[#157CFF] bg-transparent text-[#157CFE]"
}`}
style={{
width: items?.length === 3 ? '266px' : items?.length === 4 ? '192px' : items?.length === 5 ? '157px' : '266px',
height: items?.length === 3 ? '266px' : items?.length === 4 ? '192px' : items?.length === 5 ? '157px' : '270px',
}}
>
<ServiceGlyph
iconUrl={
item.icon?.__icon_url__
}
iconAlt={
item.icon?.__icon_query__
}
isActive={isActive}
/>
</div>
<h3 className="mt-[18px] text-[26px] font-medium tracking-[0.08em] text-[#232223]"
style={{
fontSize: items?.length === 3 ? '20px' : items?.length === 4 ? '16px' : items?.length === 5 ? '12px' : '20px',
}}
>
{item.heading}
</h3>
<p className="mt-[12px] max-w-[290px] text-[18px] leading-[1.08] tracking-[-0.04em] text-[#353538]"
style={{
fontSize: items?.length === 3 ? '24px' : items?.length === 4 ? '17px' : items?.length === 5 ? '14px' : '24px',
}}
>
{item.description}
</p>
</div>
{index < items?.length - 1 && (
<div className=" flex items-center px-1 "
style={{
marginTop: items?.length === 3 ? '135px' : items?.length === 4 ? '93px' : items?.length === 5 ? '70px' : '135px',
}}
>
{/* <div className="h-px mr-[-10px] w-full bg-[#4d4ef3]"
style={{
width: items?.length === 3 ? '117px' : items?.length === 4 ? '84px' : items?.length === 5 ? '60px' : '112px',
}}
/>
<svg
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="h-[18px] w-[18px] text-[#4d4ef3]"
>
<path
d="M5 4L12 9L5 14"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg> */}
<svg xmlns="http://www.w3.org/2000/svg"
style={{
width: items?.length === 3 ? '117px' : items?.length === 4 ? '84px' : items?.length === 5 ? '60px' : '112px',
}}
height="15" viewBox="0 0 119 15" fill="none">
<path d="M1 6.36401H0V8.36401H1V7.36401V6.36401ZM118.707 8.07112C119.098 7.6806 119.098 7.04743 118.707 6.65691L112.343 0.292946C111.953 -0.0975785 111.319 -0.0975785 110.929 0.292946C110.538 0.68347 110.538 1.31664 110.929 1.70716L116.586 7.36401L110.929 13.0209C110.538 13.4114 110.538 14.0446 110.929 14.4351C111.319 14.8256 111.953 14.8256 112.343 14.4351L118.707 8.07112ZM1 7.36401V8.36401H118V7.36401V6.36401H1V7.36401Z" fill="#157CFE" />
</svg>
</div>
)}
</Fragment>
);
})}
</div>
</div>
);
};
export default ServicesSlide;

View file

@ -0,0 +1,139 @@
import * as z from "zod";
export const slideLayoutId = "report-solution-slide";
export const slideLayoutName = "Report Solution Slide";
export const slideLayoutDescription =
"A solution slide with a title at the top and a main content area below. The content area supports two structural modes controlled by the showImage boolean: when true, it places one image panel on the left and two numbered content cards on the right; when false, it removes the image and displays three numbered content cards arranged across the content area. Each card contains a short step label and a descriptive text block.";
const CardSchema = z.object({
stepNumber: z.string().min(2).max(4).meta({
description: "Short card step number such as 01, 02, or 03.",
}),
description: z.string().min(20).max(90).meta({
description: "Card body copy displayed inside the feature pill.",
}),
});
export const Schema = z.object({
title: z.string().min(3).max(24).default("Solution").meta({
description: "Slide heading shown in the top-left corner.",
}),
showImage: z.boolean().default(true).meta({
description: "Controls whether the image is shown beside the cards.",
}),
featureImage: z.object({
__image_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg"),
__image_prompt__: z.string().default("Thinking woman portrait on a neutral background"),
}).default({
__image_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Thinking woman portrait on a neutral background",
}).meta({
description: "Optional image used on the left side of the slide.",
}),
cards: z
.array(CardSchema)
.min(3)
.max(3)
.default([
{
stepNumber: "01",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
stepNumber: "02",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
{
stepNumber: "03",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
])
.meta({
description:
"Three solution cards. When the image is enabled, only the first two cards are displayed.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
type SolutionSlideProps = {
data: Partial<SchemaType>;
};
function SolutionCard({
stepNumber,
description,
}: {
stepNumber: string;
description: string;
}) {
return (
<div className="flex py-[60px] px-10 w-[312px] flex-col items-center justify-center rounded-[160px] bg-[#4d4ef3] text-center text-white">
<p className="text-[42px] font-medium tracking-[8.709px]">{stepNumber}</p>
<p className="mt-[27px] text-[27px] min-h-[200px] ">
{description}
</p>
</div>
);
}
const SolutionSlide = ({ data }: SolutionSlideProps) => {
const { title, showImage, featureImage, cards } = data;
const visibleCards = showImage ? cards?.slice(0, 2) : cards;
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#F9F8F8]">
<div className="absolute left-0 top-0 w-[42px] rounded-b-[22px] bg-[#157CFF]"
style={{ height: 185 }}
/>
<div className="relative z-10 h-full py-[58px]">
{title && (
<h2 className="text-[80px] px-[64px] font-bold leading-[108.4%] tracking-[-2.419px] text-[#232223]">
{title}
</h2>
)}
<div className="mt-[70px]">
{showImage ? (
<div className="flex items-start gap-[40px]">
{featureImage?.__image_url__ && (
<div className="h-[396px] w-[534px] shrink-0 overflow-hidden rounded-r-[90px] bg-[#ece8dd]">
<img
src={featureImage?.__image_url__}
alt={featureImage?.__image_prompt__}
className="h-full w-full object-cover"
/>
</div>
)}
<div className="flex gap-[40px]">
{visibleCards?.map((card, index) => (
<SolutionCard
key={`${card.stepNumber}-${index}`}
stepNumber={card.stepNumber}
description={card.description}
/>
))}
</div>
</div>
) : (
<div className="flex justify-center gap-[44px] pt-[6px]">
{visibleCards?.map((card, index) => (
<SolutionCard
key={`${card.stepNumber}-${index}`}
stepNumber={card.stepNumber}
description={card.description}
/>
))}
</div>
)}
</div>
</div>
</div>
);
};
export default SolutionSlide;

View file

@ -0,0 +1,110 @@
import * as z from "zod";
const MemberSchema = z.object({
title: z.string().min(2).max(24).meta({
description: "Short role or title shown above the member name.",
}),
name: z.string().min(2).max(32).meta({
description: "Member name shown at the bottom of the card.",
}),
image: z.object({
__image_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg"),
__image_prompt__: z.string().default("Professional portrait of a team member"),
}).default({
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Professional portrait of a team member",
}),
});
export const slideLayoutId = "team-slide";
export const slideLayoutName = "Team Slide";
export const slideLayoutDescription =
"A team slide made of five vertical portrait cards placed side by side from edge to edge. Each card uses a full-height image background with a content overlay at the bottom containing a short title line and a larger name line.";
export const Schema = z.object({
members: z
.array(MemberSchema)
.min(5)
.max(5)
.default([
{
title: "Title",
name: "Lanny LA",
image: {
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Professional portrait of a male team member",
},
},
{
title: "Title",
name: "Lanny LA",
image: {
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Professional portrait of a female team member",
},
},
{
title: "Title",
name: "Lanny LA",
image: {
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Professional portrait of a business manager",
},
},
{
title: "Title",
name: "Lanny LA",
image: {
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Professional portrait of a senior employee",
},
},
{
title: "Title",
name: "Lanny LA",
image: {
__image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Professional portrait of a young executive",
},
},
])
.meta({
description: "Five team members rendered as portrait cards.",
}),
});
export type SchemaType = z.infer<typeof Schema>;
const TeamSlide = ({ data }: { data: Partial<SchemaType> }) => {
return (
<div className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-white">
<div className="grid h-full "
style={{ gridTemplateColumns: `repeat(${data?.members?.length}, minmax(0, 1fr))` }}
>
{data?.members?.map((member, index) => (
<div
key={`${member.name}-${index}`}
className="relative h-full overflow-hidden"
>
<img
src={member.image.__image_url__}
alt={member.image.__image_prompt__}
className="h-full w-full object-cover"
/>
<div className="absolute inset-x-0 bottom-0 h-[240px] bg-gradient-to-t from-[#4d4ef3] via-[#4d4ef3]/55 to-transparent" />
<div className="absolute left-0 bottom-0 p-[33px] text-white">
<p className="text-[21px] tracking-[2.074px] font-medium text-white/90">{member.title}</p>
<p className="mt-[14px] text-[28px] ">
{member.name}
</p>
</div>
</div>
))}
</div>
</div>
);
};
export default TeamSlide;

View file

@ -0,0 +1,338 @@
"use client";
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
Cell,
LabelList,
Line,
LineChart,
Pie,
PieChart,
ResponsiveContainer,
XAxis,
YAxis,
} from "recharts";
type SimpleBarDatum = {
label: string;
value: number;
};
type DualSeriesDatum = {
label: string;
valueA: number;
valueB: number;
};
type PieDatum = {
name: string;
value: number;
};
type PieLabelProps = {
cx?: number;
cy?: number;
midAngle?: number;
innerRadius?: number;
outerRadius?: number;
percent?: number;
};
const PRIMARY = "#4d4ef3";
const SECONDARY = "#9fb6ff";
const LIGHT = "#e8eefb";
const GRID = "#8f96aa";
function renderOutsidePieLabel({
cx = 0,
cy = 0,
midAngle = 0,
outerRadius = 0,
percent = 0,
}: PieLabelProps) {
const radius = outerRadius + 18;
const x = cx + radius * Math.cos((-midAngle * Math.PI) / 180);
const y = cy + radius * Math.sin((-midAngle * Math.PI) / 180);
return (
<text
x={x}
y={y}
fill="#7e8cb6"
textAnchor={x > cx ? "start" : "end"}
dominantBaseline="central"
fontSize="10"
fontWeight="500"
>
{(percent * 100).toFixed(1)}%
</text>
);
}
export function CompactBarChart({
data,
}: {
data: SimpleBarDatum[];
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} margin={{ top: 14, right: 10, left: -12, bottom: 8 }}>
<CartesianGrid vertical={false} stroke={GRID} strokeDasharray="3 3" />
<XAxis
dataKey="label"
tick={{ fill: "#4b5563", fontSize: 9 }}
tickLine={false}
axisLine={false}
/>
<YAxis
tick={{ fill: "#4b5563", fontSize: 9 }}
tickLine={false}
axisLine={false}
width={28}
/>
<Bar dataKey="value" fill={PRIMARY} radius={[3, 3, 0, 0]} isAnimationActive={false}>
<LabelList
dataKey="value"
position="top"
fill={PRIMARY}
fontSize={9}
offset={4}
/>
</Bar>
</BarChart>
</ResponsiveContainer>
);
}
export function WorkflowBarChart({
data,
}: {
data: SimpleBarDatum[];
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} margin={{ top: 18, right: 16, left: 0, bottom: 12 }}>
<CartesianGrid vertical={false} stroke={GRID} strokeDasharray="3 3" />
<XAxis
dataKey="label"
tick={{ fill: "#4b5563", fontSize: 12 }}
tickLine={false}
axisLine={false}
/>
<YAxis
tick={{ fill: "#4b5563", fontSize: 12 }}
tickLine={false}
axisLine={false}
width={40}
/>
<Bar dataKey="value" fill={PRIMARY} radius={[4, 4, 0, 0]} isAnimationActive={false}>
<LabelList
dataKey="value"
position="top"
fill={PRIMARY}
fontSize={11}
offset={4}
/>
</Bar>
</BarChart>
</ResponsiveContainer>
);
}
export function TrendLineChart({
data,
}: {
data: DualSeriesDatum[];
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 12, right: 8, left: -16, bottom: 10 }}>
<CartesianGrid vertical={false} stroke={GRID} strokeDasharray="3 3" />
<XAxis
dataKey="label"
tick={{ fill: "#4b5563", fontSize: 8 }}
tickLine={false}
axisLine={false}
/>
<YAxis
tick={{ fill: "#4b5563", fontSize: 8 }}
tickLine={false}
axisLine={false}
width={24}
/>
<Line
type="monotone"
dataKey="valueA"
stroke={PRIMARY}
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
<Line
type="monotone"
dataKey="valueB"
stroke={SECONDARY}
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
);
}
export function DualLineChart({
data,
}: {
data: DualSeriesDatum[];
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 24, right: 18, left: 0, bottom: 24 }}>
<CartesianGrid vertical={false} stroke={GRID} />
<XAxis
dataKey="label"
tick={{ fill: "#4b5563", fontSize: 12 }}
tickLine={false}
axisLine={false}
/>
<YAxis
tick={{ fill: "#4b5563", fontSize: 12 }}
tickLine={false}
axisLine={false}
width={40}
/>
<Line
type="monotone"
dataKey="valueA"
stroke={PRIMARY}
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
<Line
type="monotone"
dataKey="valueB"
stroke={SECONDARY}
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
);
}
export function AreaTrendChart({
data,
idPrefix,
}: {
data: SimpleBarDatum[];
idPrefix: string;
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data} margin={{ top: 18, right: 12, left: -12, bottom: 6 }}>
<defs>
<linearGradient id={`${idPrefix}-fill`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={PRIMARY} stopOpacity={0.45} />
<stop offset="100%" stopColor={PRIMARY} stopOpacity={0.05} />
</linearGradient>
</defs>
<CartesianGrid vertical={false} stroke={GRID} strokeDasharray="3 3" />
<XAxis
dataKey="label"
tick={{ fill: "#4b5563", fontSize: 8 }}
tickLine={false}
axisLine={false}
/>
<YAxis
tick={{ fill: "#4b5563", fontSize: 8 }}
tickLine={false}
axisLine={false}
width={24}
/>
<Area
type="monotone"
dataKey="value"
stroke={PRIMARY}
strokeWidth={2}
fill={`url(#${idPrefix}-fill)`}
isAnimationActive={false}
/>
</AreaChart>
</ResponsiveContainer>
);
}
export function SemiDonutChart({
data,
}: {
data: PieDatum[];
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
dataKey="value"
startAngle={180}
endAngle={0}
cx="50%"
cy="92%"
innerRadius={58}
outerRadius={88}
paddingAngle={6}
stroke="none"
labelLine={false}
label={renderOutsidePieLabel}
isAnimationActive={false}
>
{data.map((entry, index) => (
<Cell
key={`${entry.name}-${index}`}
fill={[PRIMARY, SECONDARY, LIGHT][index % 3]}
/>
))}
</Pie>
</PieChart>
</ResponsiveContainer>
);
}
export function CompactPieChart({
data,
}: {
data: PieDatum[];
}) {
return (
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
dataKey="value"
cx="50%"
cy="50%"
innerRadius={40}
outerRadius={76}
paddingAngle={1}
stroke="none"
labelLine={false}
label={renderOutsidePieLabel}
isAnimationActive={false}
>
{data.map((entry, index) => (
<Cell
key={`${entry.name}-${index}`}
fill={[PRIMARY, SECONDARY, "#d7dff4"][index % 3]}
/>
))}
</Pie>
</PieChart>
</ResponsiveContainer>
);
}

View file

@ -0,0 +1,5 @@
{
"description": "Data and narrative report layouts for intros, analysis charts, dashboards, and closing slides",
"ordered": false,
"default": false
}

View file

@ -45,6 +45,21 @@ import ProcessSlide, { Schema as PoProcessSchema, slideLayoutId as PoProcessId,
import ReportSnapshotSlide, { Schema as PoReportSnapshotSchema, slideLayoutId as PoReportSnapshotId, slideLayoutName as PoReportSnapshotName, slideLayoutDescription as PoReportSnapshotDesc } from "./ProductOverview/ReportSnapshotSlide";
import TableOfContentSlide, { Schema as PoTableOfContentSchema, slideLayoutId as PoTableOfContentId, slideLayoutName as PoTableOfContentName, slideLayoutDescription as PoTableOfContentDesc } from "./ProductOverview/TableOfContentSlide";
// Report templates
import ReportIntroSlide, { Schema as RepIntroSchema, slideLayoutId as RepIntroId, slideLayoutName as RepIntroName, slideLayoutDescription as RepIntroDesc } from "./Report/IntroSlide";
import IntroductionImageSlide, { Schema as RepIntroductionImageSchema, slideLayoutId as RepIntroductionImageId, slideLayoutName as RepIntroductionImageName, slideLayoutDescription as RepIntroductionImageDesc } from "./Report/IntroductionImageSlide";
import IntroductionStatsSlide, { Schema as RepIntroductionStatsSchema, slideLayoutId as RepIntroductionStatsId, slideLayoutName as RepIntroductionStatsName, slideLayoutDescription as RepIntroductionStatsDesc } from "./Report/IntroductionStatsSlide";
import SolutionSlide, { Schema as RepSolutionSchema, slideLayoutId as RepSolutionId, slideLayoutName as RepSolutionName, slideLayoutDescription as RepSolutionDesc } from "./Report/SolutionSlide";
import MilestoneSlide, { Schema as RepMilestoneSchema, slideLayoutId as RepMilestoneId, slideLayoutName as RepMilestoneName, slideLayoutDescription as RepMilestoneDesc } from "./Report/MilestoneSlide";
import DataAnalysisListSlide, { Schema as RepDataAnalysisListSchema, slideLayoutId as RepDataAnalysisListId, slideLayoutName as RepDataAnalysisListName, slideLayoutDescription as RepDataAnalysisListDesc } from "./Report/DataAnalysisListSlide";
import DataAnalysisBarSlide, { Schema as RepDataAnalysisBarSchema, slideLayoutId as RepDataAnalysisBarId, slideLayoutName as RepDataAnalysisBarName, slideLayoutDescription as RepDataAnalysisBarDesc } from "./Report/DataAnalysisBarSlide";
import DataAnalysisInsightBarSlide, { Schema as RepDataAnalysisInsightBarSchema, slideLayoutId as RepDataAnalysisInsightBarId, slideLayoutName as RepDataAnalysisInsightBarName, slideLayoutDescription as RepDataAnalysisInsightBarDesc } from "./Report/DataAnalysisInsightBarSlide";
import DataAnalysisLineStatsSlide, { Schema as RepDataAnalysisLineStatsSchema, slideLayoutId as RepDataAnalysisLineStatsId, slideLayoutName as RepDataAnalysisLineStatsName, slideLayoutDescription as RepDataAnalysisLineStatsDesc } from "./Report/DataAnalysisLineStatsSlide";
import DataAnalysisDashboardSlide, { Schema as RepDataAnalysisDashboardSchema, slideLayoutId as RepDataAnalysisDashboardId, slideLayoutName as RepDataAnalysisDashboardName, slideLayoutDescription as RepDataAnalysisDashboardDesc } from "./Report/DataAnalysisDashboardSlide";
import PerformanceSnapshotSlide, { Schema as RepPerformanceSnapshotSchema, slideLayoutId as RepPerformanceSnapshotId, slideLayoutName as RepPerformanceSnapshotName, slideLayoutDescription as RepPerformanceSnapshotDesc } from "./Report/PerformanceSnapshotSlide";
import ReportServicesSlide, { Schema as RepServicesSchema, slideLayoutId as RepServicesId, slideLayoutName as RepServicesName, slideLayoutDescription as RepServicesDesc } from "./Report/ServicesSlide";
import ReportTeamSlide, { Schema as RepTeamSchema, slideLayoutId as RepTeamId, slideLayoutName as RepTeamName, slideLayoutDescription as RepTeamDesc } from "./Report/TeamSlide";
// General templates
import GeneralIntroSlideLayout, { Schema as GeneralIntroSchema, layoutId as GeneralIntroId, layoutName as GeneralIntroName, layoutDescription as GeneralIntroDesc } from "./general/IntroSlideLayout";
import BasicInfoSlideLayout, { Schema as BasicInfoSchema, layoutId as BasicInfoId, layoutName as BasicInfoName, layoutDescription as BasicInfoDesc } from "./general/BasicInfoSlideLayout";
@ -220,6 +235,7 @@ import neoSwiftSettings from "./neo-swift/settings.json";
import codeSettings from "./Code/settings.json";
import educationSettings from "./Education/settings.json";
import productOverviewSettings from "./ProductOverview/settings.json";
import reportSettings from "./Report/settings.json";
// Helper to create template entry
@ -272,6 +288,22 @@ export const productOverviewTemplates: TemplateWithData[] = [
createTemplateEntry(ImageGallerySlide, PoImageGallerySchema, PoImageGalleryId, PoImageGalleryName, PoImageGalleryDesc, "product-overview", "ImageGallerySlide"),
];
export const reportTemplates: TemplateWithData[] = [
createTemplateEntry(ReportIntroSlide, RepIntroSchema, RepIntroId, RepIntroName, RepIntroDesc, "report", "IntroSlide"),
createTemplateEntry(IntroductionImageSlide, RepIntroductionImageSchema, RepIntroductionImageId, RepIntroductionImageName, RepIntroductionImageDesc, "report", "IntroductionImageSlide"),
createTemplateEntry(IntroductionStatsSlide, RepIntroductionStatsSchema, RepIntroductionStatsId, RepIntroductionStatsName, RepIntroductionStatsDesc, "report", "IntroductionStatsSlide"),
createTemplateEntry(SolutionSlide, RepSolutionSchema, RepSolutionId, RepSolutionName, RepSolutionDesc, "report", "SolutionSlide"),
createTemplateEntry(MilestoneSlide, RepMilestoneSchema, RepMilestoneId, RepMilestoneName, RepMilestoneDesc, "report", "MilestoneSlide"),
createTemplateEntry(DataAnalysisListSlide, RepDataAnalysisListSchema, RepDataAnalysisListId, RepDataAnalysisListName, RepDataAnalysisListDesc, "report", "DataAnalysisListSlide"),
createTemplateEntry(DataAnalysisBarSlide, RepDataAnalysisBarSchema, RepDataAnalysisBarId, RepDataAnalysisBarName, RepDataAnalysisBarDesc, "report", "DataAnalysisBarSlide"),
createTemplateEntry(DataAnalysisInsightBarSlide, RepDataAnalysisInsightBarSchema, RepDataAnalysisInsightBarId, RepDataAnalysisInsightBarName, RepDataAnalysisInsightBarDesc, "report", "DataAnalysisInsightBarSlide"),
createTemplateEntry(DataAnalysisLineStatsSlide, RepDataAnalysisLineStatsSchema, RepDataAnalysisLineStatsId, RepDataAnalysisLineStatsName, RepDataAnalysisLineStatsDesc, "report", "DataAnalysisLineStatsSlide"),
createTemplateEntry(DataAnalysisDashboardSlide, RepDataAnalysisDashboardSchema, RepDataAnalysisDashboardId, RepDataAnalysisDashboardName, RepDataAnalysisDashboardDesc, "report", "DataAnalysisDashboardSlide"),
createTemplateEntry(PerformanceSnapshotSlide, RepPerformanceSnapshotSchema, RepPerformanceSnapshotId, RepPerformanceSnapshotName, RepPerformanceSnapshotDesc, "report", "PerformanceSnapshotSlide"),
createTemplateEntry(ReportServicesSlide, RepServicesSchema, RepServicesId, RepServicesName, RepServicesDesc, "report", "ServicesSlide"),
createTemplateEntry(ReportTeamSlide, RepTeamSchema, RepTeamId, RepTeamName, RepTeamDesc, "report", "TeamSlide"),
];
export const neoGeneralTemplates: TemplateWithData[] = [
createTemplateEntry(TextSplitWithEmphasisBlockLayout, TextSplitWithEmphasisBlockSchema, TextSplitWithEmphasisBlockId, TextSplitWithEmphasisBlockName, TextSplitWithEmphasisBlockDesc, 'neo-general', 'TextSplitWithEmphasisBlock'),
@ -445,6 +477,7 @@ export const allLayouts: TemplateWithData[] = [
...codeTemplates,
...educationTemplates,
...productOverviewTemplates,
...reportTemplates,
];
@ -528,6 +561,13 @@ export const templates: TemplateLayoutsWithSettings[] = [
settings: productOverviewSettings as TemplateGroupSettings,
layouts: productOverviewTemplates,
},
{
id: "report",
name: "Report",
description: reportSettings.description,
settings: reportSettings as TemplateGroupSettings,
layouts: reportTemplates,
},
];
// Helper to get templates by group ID