From 974abecbc3d47dd8a44d517a99f6238cba2ce3f1 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Tue, 7 Apr 2026 00:13:47 +0545 Subject: [PATCH 01/18] feat: Code template added --- .../Code/CodeSlide01RoadmapCover.tsx | 51 +++++++ .../Code/CodeSlide02CodeExplanationSplit.tsx | 99 +++++++++++++ .../Code/CodeSlide03ApiRequestResponse.tsx | 128 ++++++++++++++++ .../Code/CodeSlide04FeatureGrid.tsx | 140 ++++++++++++++++++ .../Code/CodeSlide05ComparisonTable.tsx | 95 ++++++++++++ .../Code/CodeSlide06Workflow.tsx | 121 +++++++++++++++ .../Code/CodeSlide07UseCaseList.tsx | 70 +++++++++ .../Code/CodeSlide08CodeExplanationText.tsx | 51 +++++++ .../Code/CodeSlide09TableOfContent.tsx | 94 ++++++++++++ .../Code/CodeSlide10MetricsSplit.tsx | 90 +++++++++++ .../Code/CodeSlide11MetricsGrid.tsx | 76 ++++++++++ .../presentation-templates/Code/settings.json | 5 + .../app/presentation-templates/index.tsx | 39 ++++- 13 files changed, 1056 insertions(+), 3 deletions(-) create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/settings.json diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx new file mode 100644 index 00000000..52f779b8 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx @@ -0,0 +1,51 @@ +import * as z from "zod"; + +export const slideLayoutId = "code-roadmap-cover-slide"; +export const slideLayoutName = "Code Roadmap Cover Slide"; +export const slideLayoutDescription = + "A centered opening slide with company name, roadmap title, and supporting subtitle."; + +export const Schema = z.object({ + companyName: z.string().min(2).max(28).default("COMPANY NAME").meta({ + description: "Organization name shown above the slide title.", + }), + title: z.string().min(8).max(28).default("Development Roadmap").meta({ + description: "Primary slide heading.", + }), + subtitle: z + .string() + .min(24) + .max(92) + .default( + "We transform ideas into market-ready solutions through systematic development processes." + ) + .meta({ + description: "Supporting subtitle shown under the heading.", + }), + pageLabel: z.string().min(3).max(8).default("1 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide01RoadmapCover = ({ data }: { data: Partial }) => { + + return ( +
+
+

{data.companyName}

+

+ {data.title} +

+

{data.subtitle}

+
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide01RoadmapCover; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx new file mode 100644 index 00000000..fdee3e06 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx @@ -0,0 +1,99 @@ +import * as z from "zod"; + + +export const slideLayoutId = "code-explanation-split-slide"; +export const slideLayoutName = "Code Explanation Split Slide"; +export const slideLayoutDescription = + "A two-column slide with a code panel on the left and descriptive explanation on the right."; + +export const Schema = z.object({ + title: z.string().min(8).max(24).default("Code + Explanation").meta({ + description: "Slide heading shown at the top-left.", + }), + codeSnippet: z.object({ + language: z.string().min(2).max(10), + fileName: z.string().min(3).max(30), + content: z.string().min(20).max(520), + }).default({ + language: "tsx", + fileName: "components/UserAuth.tsx", + content: `import { useState } from "react"; +import { login } from "@/lib/auth"; + +export function UserAuth() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const user = await login(email, password); + console.log("Logged in:", user); + }; + + return null; +} + +`, + }).meta({ + description: "Code sample shown in the left panel.", + }), + explanationTitle: z.string().min(4).max(20).default("Explanation").meta({ + description: "Heading shown above the explanatory paragraph.", + }), + explanation: z + .string() + .min(40) + .max(360) + .default( + "This component manages credentials as local state and submits them through an async handler. The login utility abstracts network details while the handler keeps the UI flow predictable. Keep validation and side effects isolated so changes remain safe when authentication requirements evolve." + ) + .meta({ + description: "Explanation paragraph shown in the right column.", + }), + pageLabel: z.string().min(3).max(8).default("2 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide02CodeExplanationSplit = ({ + data, +}: { + data: Partial; +}) => { + + return ( +
+ +
+

{data.title}

+ +
+
+

{data.codeSnippet?.fileName}

+
+
+              
+                {data.codeSnippet?.content}
+              
+            
+
+ +
+

{data.explanationTitle}

+

+ {data.explanation} +

+
+
+
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide02CodeExplanationSplit; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx new file mode 100644 index 00000000..42e9f206 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx @@ -0,0 +1,128 @@ +import * as z from "zod"; + + +export const slideLayoutId = "code-api-request-response-slide"; +export const slideLayoutName = "Code API Request Response Slide"; +export const slideLayoutDescription = + "An API-focused slide with endpoint metadata, request payload, and response payload."; + +export const Schema = z.object({ + title: z.string().min(8).max(26).default("API Request / Response").meta({ + description: "Main heading shown at the top-left.", + }), + method: z.enum(["GET", "POST", "PATCH", "DELETE"]).default("POST").meta({ + description: "HTTP method badge text.", + }), + endpoint: z.string().min(8).max(48).default("/api/v1/users/authenticate").meta({ + description: "Endpoint path text.", + }), + headers: z + .array(z.string().min(12).max(44)) + .min(2) + .max(2) + .default(["Content-Type: application/json", "Authorization: Bearer "]) + .meta({ + description: "Two header lines shown in the endpoint card.", + }), + requestSnippet: z.object({ + language: z.string().min(2).max(10), + fileName: z.string().min(3).max(24), + content: z.string().min(20).max(220), + }).default({ + language: "json", + fileName: "request.json", + content: `{ + "email": "user@example.com user@example.com user@example.com user@example.com user@example.com" , + "password": "securepassword123" +}`, + }).meta({ + description: "Request payload example.", + }), + responseSnippet: z.object({ + language: z.string().min(2).max(10), + fileName: z.string().min(3).max(24), + content: z.string().min(20).max(620), + }).default({ + language: "json", + fileName: "response.json", + content: `{ + "success": true, + "user": { + "id": "usr_1234567890", + "email": "user@example.com", + "name": "John Doe", + "role": "admin" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expiresIn": 3600 +}`, + }).meta({ + description: "Response payload example.", + }), + pageLabel: z.string().min(3).max(8).default("3 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide03ApiRequestResponse = ({ + data, +}: { + data: Partial; +}) => { + + return ( +
+ +
+

{data.title}

+ +
+
+
+
+

+ {data.method} +

+

{data.endpoint}

+
+

Headers

+
+ {data.headers?.map((item) => ( +

{item}

+ ))} +
+
+ +
+

{data.requestSnippet?.fileName}

+
+
+                
+                  {data.requestSnippet?.content}
+                
+              
+
+
+ +
+

{data.responseSnippet?.fileName}

+
+
+              
+                {data.responseSnippet?.content}
+              
+            
+
+
+
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide03ApiRequestResponse; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx new file mode 100644 index 00000000..b7700234 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx @@ -0,0 +1,140 @@ +import * as z from "zod"; + +const FeatureCardSchema = z.object({ + title: z.string().min(3).max(20).meta({ + description: "Feature title shown on each card.", + }), + description: z.string().min(18).max(82).meta({ + description: "Supporting feature description.", + }), + icon: z.object({ + __icon_url__: z.string().min(10).max(180).meta({ + description: "URL to icon", + }), + __icon_query__: z.string().min(3).max(28).meta({ + description: "Query used to search the icon", + }), + }).default({ + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }).meta({ + description: "Icon used for each feature bullet in plan cards.", + }), +}); + +export const slideLayoutId = "code-feature-grid-slide"; +export const slideLayoutName = "Code Feature Grid Slide"; +export const slideLayoutDescription = + "A six-card feature summary grid with icon badges and compact descriptions."; + +export const Schema = z.object({ + title: z.string().min(6).max(20).default("Feature Grid").meta({ + description: "Slide title shown above the grid.", + }), + features: z + .array(FeatureCardSchema) + .min(3) + .max(6) + .default([ + { + title: "Modern Stack", + description: "Built with React, TypeScript, and Tailwind CSS for maximum developer experience.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Component Library", + description: "Reusable UI components with consistent design patterns.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "CLI Tools", + description: "Command-line utilities for scaffolding and automation.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Analytics", + description: "Built-in tracking and performance monitoring.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Version Control", + description: "Git-based workflow with automated deployments.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Best Practices", + description: "Following industry standards and modern development patterns.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + ]) + .meta({ + description: "Six feature cards displayed in a 3x2 grid.", + }), + pageLabel: z.string().min(3).max(8).default("4 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide04FeatureGrid = ({ data }: { data: Partial }) => { + + + return ( +
+ + +

{data.title}

+ +
+ {data?.features?.map((feature) => ( +
+
+

{feature.title}

+ + {feature.icon.__icon_query__} + +
+

{feature.description}

+
+ ))} +
+ + +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide04FeatureGrid; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx new file mode 100644 index 00000000..f76a12c0 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx @@ -0,0 +1,95 @@ +import * as z from "zod"; + +const ComparisonRowSchema = z.object({ + feature: z.string().min(4).max(20).meta({ + description: "Feature label shown in the first column.", + }), + react: z.string().min(1).max(12).meta({ + description: "React cell value.", + }), + vue: z.string().min(1).max(12).meta({ + description: "Vue cell value.", + }), + angular: z.string().min(1).max(12).meta({ + description: "Angular cell value.", + }), +}); + +export const slideLayoutId = "code-comparison-table-slide"; +export const slideLayoutName = "Code Comparison Table Slide"; +export const slideLayoutDescription = + "A framework comparison table with feature rows and highlighted compatibility marks."; + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Comparison").meta({ + description: "Slide title shown above the table.", + }), + rows: z + .array(ComparisonRowSchema) + .min(6) + .max(6) + .default([ + { feature: "Component-based", react: "check", vue: "check", angular: "check" }, + { feature: "TypeScript Support", react: "check", vue: "check", angular: "check" }, + { feature: "Learning Curve", react: "Medium", vue: "Easy", angular: "Steep" }, + { feature: "Bundle Size", react: "40KB", vue: "34KB", angular: "167KB" }, + { feature: "Performance", react: "Excellent", vue: "Excellent", angular: "Good" }, + { feature: "Community Size", react: "Huge", vue: "Large", angular: "Large" }, + ]) + .meta({ + description: "Six comparison rows shown in the table.", + }), + pageLabel: z.string().min(3).max(8).default("5 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +function renderCell(value: string) { + if (value.toLowerCase() === "check") { + return ; + } + + return {value}; +} + +const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) => { + + return ( +
+ +

{data.title}

+ +
+
+

Feature

+

React

+

Vue

+

Angular

+
+ +
+ {data?.rows?.map((row) => ( +
+

{row.feature}

+
{renderCell(row.react)}
+
{renderCell(row.vue)}
+
{renderCell(row.angular)}
+
+ ))} +
+ +
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide05ComparisonTable; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx new file mode 100644 index 00000000..15ad1f1a --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx @@ -0,0 +1,121 @@ +import { Fragment } from "react"; +import * as z from "zod"; + +const WorkflowStepSchema = z.object({ + title: z.string().min(3).max(12).meta({ + description: "Step title shown in each workflow card.", + }), + description: z.string().min(18).max(58).meta({ + description: "Short step description text.", + }), + icon: z.object({ + __icon_url__: z.string().min(10).max(180), + __icon_query__: z.string().min(3).max(28), + }).default({ + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }), +}); + +export const slideLayoutId = "code-workflow-slide"; +export const slideLayoutName = "Code Workflow Slide"; +export const slideLayoutDescription = + "A four-step workflow slide with cards and directional arrows between steps."; + +export const Schema = z.object({ + title: z.string().min(6).max(16).default("Workflow").meta({ + description: "Slide title shown above the workflow row.", + }), + steps: z + .array(WorkflowStepSchema) + .min(4) + .max(4) + .default([ + { + title: "Design", + description: "Create wireframes and design system components.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Develop", + description: "Build features using modern frameworks and best practices.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Test & QA", + description: "Run automated tests and quality assurance checks.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + { + title: "Deploy", + description: "Ship to production with CI and CD pipeline automation.", + icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }, + }, + ]) + .meta({ + description: "Four workflow steps shown in sequence.", + }), + pageLabel: z.string().min(3).max(8).default("6 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide06Workflow = ({ data }: { data: Partial }) => { + + return ( +
+ +

{data.title}

+ +
+ {data?.steps?.map((step, index) => ( + +
+
+ {step.icon.__icon_query__} +
+

{step.title}

+

{step.description}

+
+ {index < (data?.steps?.length || 0) - 1 && ( + + + + + )} +
+ ))} + +
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide06Workflow; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx new file mode 100644 index 00000000..69013541 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx @@ -0,0 +1,70 @@ +import * as z from "zod"; + +export const slideLayoutId = "code-use-case-list-slide"; +export const slideLayoutName = "Code Use Case List Slide"; +export const slideLayoutDescription = + "A two-column numbered use-case list with eight compact items."; + +export const Schema = z.object({ + title: z.string().min(6).max(16).default("Usecase").meta({ + description: "Slide title shown above the numbered list.", + }), + items: z + .array(z.string().min(16).max(58)) + .min(4) + .max(8) + .default([ + "Use pre-built component library for UI consistency", + "Integrate REST API with TypeScript for type safety", + "Implement real-time updates using WebSocket", + "Deploy to production with automated CI/CD pipeline", + "Enable role-based permissions for protected actions", + "Generate docs automatically from route contracts", + "Track release health with telemetry dashboards", + "Add rollback strategy for high-risk deployments", + ]) + .meta({ + description: "Eight use-case items shown in two columns.", + }), + pageLabel: z.string().min(3).max(8).default("7 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide07UseCaseList = ({ data }: { data: Partial }) => { + + return ( +
+ + +

{data.title}

+ +
+ {data?.items?.map((item, index) => ( +
+ + {index + 1} + +

{item}

+
+ ))} +
+ + +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide07UseCaseList; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx new file mode 100644 index 00000000..0cf80abb --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx @@ -0,0 +1,51 @@ +import * as z from "zod"; + +export const slideLayoutId = "code-explanation-text-slide"; +export const slideLayoutName = "Code Explanation Text Slide"; +export const slideLayoutDescription = + "A text-only explanation slide with generous whitespace for narrative documentation."; + +export const Schema = z.object({ + title: z.string().min(8).max(24).default("Code + Explanation").meta({ + description: "Main slide title shown at the top-left.", + }), + explanationTitle: z.string().min(4).max(20).default("Explanation").meta({ + description: "Subheading above the paragraph body.", + }), + explanation: z + .string() + .min(60) + .max(360) + .default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ) + .meta({ + description: "Long-form explanation body.", + }), + pageLabel: z.string().min(3).max(8).default("8 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide08CodeExplanationText = ({ data }: { data: Partial }) => { + + return ( +
+ + +

{data.title}

+
+

{data.explanationTitle}

+

{data.explanation}

+
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide08CodeExplanationText; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx new file mode 100644 index 00000000..aa72323d --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx @@ -0,0 +1,94 @@ +import * as z from "zod"; + +export const slideLayoutId = "code-table-of-content-slide"; +export const slideLayoutName = "Code Table Of Content Slide"; +export const slideLayoutDescription = + "A two-column table of contents with numbered entries and folder-style bullets."; + +export const Schema = z.object({ + title: z.string().min(8).max(24).default("Table of Content").meta({ + description: "Slide heading shown above the index list.", + }), + items: z + .array(z.object({ + number: z.string().min(2).max(2), + label: z.string().min(3).max(16), + description: z.string().min(3).max(18), + })) + .min(12) + .max(12) + .default([ + { number: "01", label: "Content 1", description: "Section summary" }, + { number: "02", label: "Content 2", description: "Section summary" }, + { number: "03", label: "Content 3", description: "Section summary" }, + { number: "04", label: "Content 4", description: "Section summary" }, + { number: "05", label: "Content 5", description: "Section summary" }, + { number: "06", label: "Content 6", description: "Section summary" }, + { number: "07", label: "Content 7", description: "Section summary" }, + { number: "08", label: "Content 8", description: "Section summary" }, + { number: "09", label: "Content 9", description: "Section summary" }, + { number: "10", label: "Content 10", description: "Section summary" }, + { number: "11", label: "Content 11", description: "Section summary" }, + { number: "12", label: "Content 12", description: "Section summary" }, + ]) + .meta({ + description: "Left-column table of contents entries.", + }), + + pageLabel: z.string().min(3).max(8).default("9 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +function TocColumn({ items }: { items: { number: string; label: string, description?: string }[] }) { + return ( +
+ {items.map((item, index) => { + + + return ( +
+
+ +
+
+ +

{item.number}

+

{item.label}

+ {item.description &&

{item.description}

} +
+
+ ); + })} +
+ ); +} + +const CodeSlide09TableOfContent = ({ data }: { data: Partial }) => { + const leftItems = data?.items?.slice(0, data?.items?.length / 2); + const rightItems = data?.items?.slice(data?.items?.length / 2); + + + return ( +
+ + +
+

{data.title}

+ +
+ + +
+
+ +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide09TableOfContent; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx new file mode 100644 index 00000000..66569317 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx @@ -0,0 +1,90 @@ +import * as z from "zod"; + +const MetricSchema = z.object({ + value: z.string().min(2).max(8).meta({ + description: "Primary metric value.", + }), + label: z.string().min(3).max(14).meta({ + description: "Metric label text.", + }), + period: z.string().min(3).max(16).meta({ + description: "Metric period text.", + }), +}); + +export const slideLayoutId = "code-metrics-split-slide"; +export const slideLayoutName = "Code Metrics Split Slide"; +export const slideLayoutDescription = + "A metrics slide with narrative text on the left and two stat cards on the right."; + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Metrics").meta({ + description: "Slide title shown at the top-left.", + }), + explanationTitle: z.string().min(4).max(16).default("Explanation").meta({ + description: "Heading above the explanatory paragraph.", + }), + explanation: z + .string() + .min(60) + .max(320) + .default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ) + .meta({ + description: "Body text for the narrative section.", + }), + metrics: z + .array(MetricSchema) + .min(2) + .max(2) + .default([ + { value: "50k+", label: "Active Users", period: "Last 12 months" }, + { value: "50k+", label: "Active Users", period: "Last 12 months" }, + ]) + .meta({ + description: "Two metric cards shown in the right column.", + }), + pageLabel: z.string().min(3).max(8).default("10 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide10MetricsSplit = ({ data }: { data: Partial }) => { + const slideData = Schema.parse(data); + + return ( +
+ + +

{slideData.title}

+
+
+

{slideData.explanationTitle}

+

{slideData.explanation}

+
+ +
+ {slideData.metrics.map((metric, index) => ( +
+

{metric.value}

+

{metric.label}

+

{metric.period}

+
+ ))} +
+
+ +
+ {slideData.pageLabel} +
+
+ ); +}; + +export default CodeSlide10MetricsSplit; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx new file mode 100644 index 00000000..6dd3ea05 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx @@ -0,0 +1,76 @@ +import * as z from "zod"; + +const MetricSchema = z.object({ + value: z.string().min(2).max(8).meta({ + description: "Primary metric value.", + }), + label: z.string().min(3).max(14).meta({ + description: "Metric label text.", + }), + period: z.string().min(3).max(16).meta({ + description: "Metric period text.", + }), +}); + +export const slideLayoutId = "code-metrics-grid-slide"; +export const slideLayoutName = "Code Metrics Grid Slide"; +export const slideLayoutDescription = + "A six-card metrics grid for KPI overviews in code-focused decks."; + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Metrics").meta({ + description: "Slide heading shown above the KPI cards.", + }), + metrics: z + .array(MetricSchema) + .min(3) + .max(6) + .default([ + { value: "99.9%", label: "Uptime", period: "Last 12 months" }, + { value: "<100ms", label: "Response Time", period: "Last 12 months" }, + { value: "50k+", label: "Active Users", period: "Last 12 months" }, + { value: "99.9%", label: "Uptime", period: "Last 12 months" }, + { value: "<100ms", label: "Response Time", period: "Last 12 months" }, + { value: "50k+", label: "Active Users", period: "Last 12 months" }, + ]) + .meta({ + description: "Six KPI cards in a 3x2 grid.", + }), + pageLabel: z.string().min(3).max(8).default("11 / 11").meta({ + description: "Bottom pagination label.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide11MetricsGrid = ({ data }: { data: Partial }) => { + + return ( +
+ + + +

{data.title}

+ +
+ {data?.metrics?.map((metric, index) => ( +
+

{metric.value}

+

{metric.label}

+

{metric.period}

+
+ ))} +
+ + +
+ {data.pageLabel} +
+
+ ); +}; + +export default CodeSlide11MetricsGrid; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/settings.json b/electron/servers/nextjs/app/presentation-templates/Code/settings.json new file mode 100644 index 00000000..e73cc025 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/settings.json @@ -0,0 +1,5 @@ +{ + "description": "Developer-focused layouts for roadmaps, APIs, code explanations, and technical metrics", + "ordered": false, + "default": false +} diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index 4af28183..60131f0d 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -3,6 +3,19 @@ import { TemplateWithData, TemplateGroupSettings, createTemplateEntry, TemplateL // TODO: Step 1: Import All templates Layouts Here (like the ones below) +// Code templates +import CodeSlide01RoadmapCover, { Schema as CodeRoadmapCoverSchema, slideLayoutId as CodeRoadmapCoverId, slideLayoutName as CodeRoadmapCoverName, slideLayoutDescription as CodeRoadmapCoverDesc } from "./Code/CodeSlide01RoadmapCover"; +import CodeSlide02CodeExplanationSplit, { Schema as CodeExplanationSplitSchema, slideLayoutId as CodeExplanationSplitId, slideLayoutName as CodeExplanationSplitName, slideLayoutDescription as CodeExplanationSplitDesc } from "./Code/CodeSlide02CodeExplanationSplit"; +import CodeSlide03ApiRequestResponse, { Schema as CodeApiRequestResponseSchema, slideLayoutId as CodeApiRequestResponseId, slideLayoutName as CodeApiRequestResponseName, slideLayoutDescription as CodeApiRequestResponseDesc } from "./Code/CodeSlide03ApiRequestResponse"; +import CodeSlide04FeatureGrid, { Schema as CodeFeatureGridSchema, slideLayoutId as CodeFeatureGridId, slideLayoutName as CodeFeatureGridName, slideLayoutDescription as CodeFeatureGridDesc } from "./Code/CodeSlide04FeatureGrid"; +import CodeSlide05ComparisonTable, { Schema as CodeComparisonTableSchema, slideLayoutId as CodeComparisonTableId, slideLayoutName as CodeComparisonTableName, slideLayoutDescription as CodeComparisonTableDesc } from "./Code/CodeSlide05ComparisonTable"; +import CodeSlide06Workflow, { Schema as CodeWorkflowSchema, slideLayoutId as CodeWorkflowId, slideLayoutName as CodeWorkflowName, slideLayoutDescription as CodeWorkflowDesc } from "./Code/CodeSlide06Workflow"; +import CodeSlide07UseCaseList, { Schema as CodeUseCaseListSchema, slideLayoutId as CodeUseCaseListId, slideLayoutName as CodeUseCaseListName, slideLayoutDescription as CodeUseCaseListDesc } from "./Code/CodeSlide07UseCaseList"; +import CodeSlide08CodeExplanationText, { Schema as CodeExplanationTextSchema, slideLayoutId as CodeExplanationTextId, slideLayoutName as CodeExplanationTextName, slideLayoutDescription as CodeExplanationTextDesc } from "./Code/CodeSlide08CodeExplanationText"; +import CodeSlide09TableOfContent, { Schema as CodeTableOfContentSchema, slideLayoutId as CodeTableOfContentId, slideLayoutName as CodeTableOfContentName, slideLayoutDescription as CodeTableOfContentDesc } from "./Code/CodeSlide09TableOfContent"; +import CodeSlide10MetricsSplit, { Schema as CodeMetricsSplitSchema, slideLayoutId as CodeMetricsSplitId, slideLayoutName as CodeMetricsSplitName, slideLayoutDescription as CodeMetricsSplitDesc } from "./Code/CodeSlide10MetricsSplit"; +import CodeSlide11MetricsGrid, { Schema as CodeMetricsGridSchema, slideLayoutId as CodeMetricsGridId, slideLayoutName as CodeMetricsGridName, slideLayoutDescription as CodeMetricsGridDesc } from "./Code/CodeSlide11MetricsGrid"; + // 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"; @@ -175,6 +188,7 @@ import neoGeneralSettings from "./neo-general/settings.json"; import neoStandardSettings from "./neo-standard/settings.json"; import neoModernSettings from "./neo-modern/settings.json"; import neoSwiftSettings from "./neo-swift/settings.json"; +import codeSettings from "./Code/settings.json"; // Helper to create template entry @@ -182,6 +196,20 @@ import neoSwiftSettings from "./neo-swift/settings.json"; // TODO: Step 3: Create template entries for each template (like the ones below) +export const codeTemplates: TemplateWithData[] = [ + createTemplateEntry(CodeSlide01RoadmapCover, CodeRoadmapCoverSchema, CodeRoadmapCoverId, CodeRoadmapCoverName, CodeRoadmapCoverDesc, "code", "CodeSlide01RoadmapCover"), + createTemplateEntry(CodeSlide02CodeExplanationSplit, CodeExplanationSplitSchema, CodeExplanationSplitId, CodeExplanationSplitName, CodeExplanationSplitDesc, "code", "CodeSlide02CodeExplanationSplit"), + createTemplateEntry(CodeSlide03ApiRequestResponse, CodeApiRequestResponseSchema, CodeApiRequestResponseId, CodeApiRequestResponseName, CodeApiRequestResponseDesc, "code", "CodeSlide03ApiRequestResponse"), + createTemplateEntry(CodeSlide04FeatureGrid, CodeFeatureGridSchema, CodeFeatureGridId, CodeFeatureGridName, CodeFeatureGridDesc, "code", "CodeSlide04FeatureGrid"), + createTemplateEntry(CodeSlide05ComparisonTable, CodeComparisonTableSchema, CodeComparisonTableId, CodeComparisonTableName, CodeComparisonTableDesc, "code", "CodeSlide05ComparisonTable"), + createTemplateEntry(CodeSlide06Workflow, CodeWorkflowSchema, CodeWorkflowId, CodeWorkflowName, CodeWorkflowDesc, "code", "CodeSlide06Workflow"), + createTemplateEntry(CodeSlide07UseCaseList, CodeUseCaseListSchema, CodeUseCaseListId, CodeUseCaseListName, CodeUseCaseListDesc, "code", "CodeSlide07UseCaseList"), + createTemplateEntry(CodeSlide08CodeExplanationText, CodeExplanationTextSchema, CodeExplanationTextId, CodeExplanationTextName, CodeExplanationTextDesc, "code", "CodeSlide08CodeExplanationText"), + createTemplateEntry(CodeSlide09TableOfContent, CodeTableOfContentSchema, CodeTableOfContentId, CodeTableOfContentName, CodeTableOfContentDesc, "code", "CodeSlide09TableOfContent"), + createTemplateEntry(CodeSlide10MetricsSplit, CodeMetricsSplitSchema, CodeMetricsSplitId, CodeMetricsSplitName, CodeMetricsSplitDesc, "code", "CodeSlide10MetricsSplit"), + createTemplateEntry(CodeSlide11MetricsGrid, CodeMetricsGridSchema, CodeMetricsGridId, CodeMetricsGridName, CodeMetricsGridDesc, "code", "CodeSlide11MetricsGrid"), +]; + export const neoGeneralTemplates: TemplateWithData[] = [ createTemplateEntry(TextSplitWithEmphasisBlockLayout, TextSplitWithEmphasisBlockSchema, TextSplitWithEmphasisBlockId, TextSplitWithEmphasisBlockName, TextSplitWithEmphasisBlockDesc, 'neo-general', 'TextSplitWithEmphasisBlock'), @@ -352,8 +380,7 @@ export const allLayouts: TemplateWithData[] = [ ...modernTemplates, ...standardTemplates, ...swiftTemplates, - - + ...codeTemplates, ]; @@ -416,7 +443,13 @@ export const templates: TemplateLayoutsWithSettings[] = [ settings: swiftSettings as TemplateGroupSettings, layouts: swiftTemplates, }, - + { + id: "code", + name: "Code", + description: codeSettings.description, + settings: codeSettings as TemplateGroupSettings, + layouts: codeTemplates, + }, ]; // Helper to get templates by group ID From 9061bf3e6f9b43734eee1e0a7ebbd5b089b6ebbe Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Tue, 7 Apr 2026 11:00:33 +0545 Subject: [PATCH 02/18] feat: Education Template added --- .../Education/EducationAboutSlide.tsx | 112 +++++++++++ .../Education/EducationContentSplitSlide.tsx | 75 ++++++++ .../Education/EducationCoverSlide.tsx | 54 ++++++ .../Education/EducationImageGallerySlide.tsx | 79 ++++++++ .../Education/EducationReportDonutSlide.tsx | 130 +++++++++++++ .../Education/EducationServicesSplitSlide.tsx | 146 ++++++++++++++ .../EducationStatisticsGridSlide.tsx | 153 +++++++++++++++ .../EducationTableOfContentsSlide.tsx | 81 ++++++++ .../Education/EducationTimelineSlide.tsx | 182 ++++++++++++++++++ .../Education/settings.json | 5 + .../app/presentation-templates/index.tsx | 32 +++ 11 files changed, 1049 insertions(+) create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/settings.json diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx new file mode 100644 index 00000000..b8502868 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx @@ -0,0 +1,112 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-about-slide"; +export const slideLayoutName = "Education About Slide"; +export const slideLayoutDescription = + "A left text column with company introduction and a right-side visual grid made from one repeated image and tinted text panels."; + +export const Schema = z.object({ + companyName: z.string().min(3).max(22).default("Company Name").meta({ + description: "Main heading in the left content column.", + }), + intro: z.string().min(40).max(120).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et." + ).meta({ + description: "Bold intro text shown beneath the company heading.", + }), + body: z.string().min(120).max(280).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi." + ).meta({ + description: "Body paragraph in the left content section.", + }), + topPanelText: z.string().min(20).max(70).default("Insert info about the company.").meta({ + description: "Short text inside the top-right dark panel. ", + }), + bottomPanelText: z.string().min(20).max(70).default("Insert info about the company and your mission statement.").meta({ + description: "Short text inside the bottom-right dark panel.", + }), + topFeatureImage: z.object({ + __image_url__: z.string().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), + __image_prompt__: z.string().min(10).max(200).default("Office team collaboration"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Office team collaboration", + }).meta({ + description: "Single image reused in the right-side visual grid.", + }), + bottomFeatureImage: z.object({ + __image_url__: z.string().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), + __image_prompt__: z.string().min(10).max(200).default("Office team collaboration"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Office team collaboration", + }).meta({ + description: "Single image reused in the right-side visual grid.", + }), +}); + +export type SchemaType = z.infer; + +const EducationAboutSlide = ({ data }: { data: Partial }) => { + + + return (<> + + +
+
+
+

+ {data.companyName} +

+

+ {data.intro} +

+

+ {data.body} +

+
+ +
+
+ {data.topFeatureImage?.__image_prompt__} +
+

+ {data.topPanelText} +

+
+
+ +
+
+ +
+ {data.bottomFeatureImage?.__image_prompt__} +
+ +
+
+

+ {data.bottomPanelText} +

+
+
+
+
+
+ + ); +}; + +export default EducationAboutSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx new file mode 100644 index 00000000..0061b169 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx @@ -0,0 +1,75 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-content-split-slide"; +export const slideLayoutName = "Education Content Split Slide"; +export const slideLayoutDescription = + "A left collage built from one repeated image and a right content block containing heading, tagline, and paragraph text."; + +export const Schema = z.object({ + heading: z.string().min(3).max(16).default("Heading").meta({ + description: "Main right-side heading.", + }), + tagline: z.string().min(3).max(12).default("TAGLINE").meta({ + description: "Small uppercase label shown under the heading.", + }), + body: z.string().min(80).max(300).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ).meta({ + description: "Main descriptive paragraph on the right side.", + }), + collageImage: z.object({ + __image_url__: z.string().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), + __image_prompt__: z.string().min(10).max(200).default("Business team around a laptop"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Business team around a laptop", + }).meta({ + description: "Single image reused to create the left collage composition.", + }), +}); + +export type SchemaType = z.infer; + +const EducationContentSplitSlide = ({ data }: { data: Partial }) => { + const { heading, tagline, body, collageImage } = data; + + return ( +
+
+
+
+ {collageImage?.__image_prompt__} +
+
+
{collageImage?.__image_prompt__}
+
{collageImage?.__image_prompt__}
+
+
+ +
+

{data.heading}

+

+ {data.tagline} +

+

{body}

+
+
+
+ ); +}; + +export default EducationContentSplitSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx new file mode 100644 index 00000000..5e1ced4e --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx @@ -0,0 +1,54 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-cover-slide"; +export const slideLayoutName = "Education Cover Slide"; +export const slideLayoutDescription = + "A full-bleed cover slide with a single background image, a strong violet overlay, and centered company/title text."; + +export const Schema = z.object({ + companyName: z.string().min(3).max(24).default("COMPANY NAME").meta({ + description: "Small uppercase company label shown above the main title.", + }), + title: z.string().min(6).max(32).default("PowerPoint Template").meta({ + description: "Main centered title of the cover slide.", + }), + backgroundImage: z.object({ + __image_url__: z.string().default("https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80"), + __image_prompt__: z.string().min(10).max(200).default("City business district buildings"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80", + __image_prompt__: "City business district buildings", + }).meta({ + description: "Single background image used across the cover.", + }), +}); + +export type SchemaType = z.infer; + +const EducationCoverSlide = ({ data }: { data: Partial }) => { + const { companyName, title, backgroundImage } = data; + + return ( +
+ {backgroundImage?.__image_prompt__} + +
+ + +
+

{data.companyName}

+

+ {title} +

+
+
+ ); +}; + +export default EducationCoverSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx new file mode 100644 index 00000000..0f12403b --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx @@ -0,0 +1,79 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-image-gallery-slide"; +export const slideLayoutName = "Education Image Gallery Slide"; +export const slideLayoutDescription = + "A slide with a left image collage (one repeated image) and right text block for gallery heading and description."; + +export const Schema = z.object({ + title: z.string().min(3).max(16).default("Image Gallery").meta({ + description: "Heading on the right side.", + }), + body: z.string().min(70).max(200).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris." + ).meta({ + description: "Supporting paragraph shown below the heading.", + }), + galleryImages: z.array(z.object({ + __image_url__: z.string(), + __image_prompt__: z.string(), + })).max(5).min(5).default(Array(5).fill({ + __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Office team collaboration", + })).meta({ + description: "Image gallery images.", + }), +}); + +export type SchemaType = z.infer; + +const EducationImageGallerySlide = ({ data }: { data: Partial }) => { + + const { title, body, galleryImages } = data; + + return ( +
+
+
+ {galleryImages?.[0].__image_prompt__} + {galleryImages?.[1].__image_prompt__} + {galleryImages?.[2].__image_prompt__} + {galleryImages?.[3].__image_prompt__} + {galleryImages?.[4].__image_prompt__} +
+ +
+

+ {title} +

+

+ {body} +

+
+
+
+ ); +}; + +export default EducationImageGallerySlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx new file mode 100644 index 00000000..7bff8123 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx @@ -0,0 +1,130 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-report-donut-slide"; +export const slideLayoutName = "Education Report Donut Slide"; +export const slideLayoutDescription = + "A report slide with left-side title/content and a right-side donut chart with legend values."; + +const SegmentSchema = z.object({ + label: z.string().min(3).max(12).meta({ + description: "Legend label for one donut chart segment.", + }), + value: z.number().min(1).max(100).meta({ + description: "Percentage value for one chart segment.", + }), + color: z.string().min(4).max(20).meta({ + description: "Hex color value for one chart segment.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(3).max(14).default("Report").meta({ + description: "Main heading in the left content area.", + }), + body: z.string().min(80).max(220).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris." + ).meta({ + description: "Main report paragraph on the left.", + }), + footnote: z.string().min(20).max(110).default( + "(Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt.)" + ).meta({ + description: "Footnote text shown at the bottom of the left area.", + }), + chartTitle: z.string().min(8).max(26).default("Students by Grade Level").meta({ + description: "Heading shown above the donut chart.", + }), + dateRange: z.string().min(8).max(22).default("Apr 10 - Apr 17").meta({ + description: "Date range label under the chart heading.", + }), + segments: z + .array(SegmentSchema) + .min(4) + .max(4) + .default([ + { label: "Option A", value: 17.07, color: "#4A15A8" }, + { label: "Option B", value: 45.23, color: "#5B45AD" }, + { label: "Option C", value: 21.61, color: "#876FC1" }, + { label: "Option D", value: 16.36, color: "#A89ACF" }, + ]) + .meta({ + description: "Four donut segments with labels and percentages.", + }), + +}); + +export type SchemaType = z.infer; + +const EducationReportDonutSlide = ({ data }: { data: Partial }) => { + const { title, body, footnote, chartTitle, dateRange, segments } = data; + + const total = segments?.reduce((sum, item) => sum + item.value, 0) || 0; + let cursor = 0; + const conicStops = segments + ?.map((segment) => { + const start = cursor; + const span = total > 0 ? (segment.value / total) * 100 : 0; + cursor += span; + return `${segment.color} ${start}% ${cursor}%`; + }) + .join(", "); + + return ( +
+
+
+
+ + +
+ +
+

+ {title} +

+ +

+ {body} +

+
+ +

+ {footnote} +

+
+ +
+

+ {chartTitle} +

+

{dateRange}

+ +
+
+
+
+
+ +
+ {segments?.map((segment, index) => ( +
+ + {segment.label} + + {segment.value.toFixed(2)}% + +
+ ))} +
+
+
+ +
+ ); +}; + +export default EducationReportDonutSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx new file mode 100644 index 00000000..ac5bc8fd --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx @@ -0,0 +1,146 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-services-split-slide"; +export const slideLayoutName = "Education Services Split Slide"; +export const slideLayoutDescription = + "A services layout with left heading, one repeated image column, and two stacked service description blocks on the right."; + +const ServiceSchema = z.object({ + serviceImage: z.object({ + __image_url__: z.string(), + __image_prompt__: z.string(), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Team meeting image reused across two rows", + }).meta({ + description: "Single image reused in the middle column.", + }), + heading: z.string().min(3).max(18).meta({ + description: "Service heading shown in the right column.", + }), + tagline: z.string().min(3).max(12).meta({ + description: "Short label under each service heading.", + }), + body: z.string().min(40).max(90).meta({ + description: "Service description paragraph.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(4).max(12).default("Services").meta({ + description: "Main slide title shown on the left.", + }), + sections: z + .array(ServiceSchema) + .min(2) + .max(4) + .default([ + { + serviceImage: { + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Team meeting image reused across two rows", + }, + heading: "Service 1", + tagline: "TAGLINE", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", + }, + { + serviceImage: { + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Team meeting image reused across two rows", + }, + heading: "Service 2", + tagline: "TAGLINE", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", + }, + { + serviceImage: { + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Team meeting image reused across two rows", + }, + heading: "Service 3", + tagline: "TAGLINE", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", + }, + { + serviceImage: { + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Team meeting image reused across two rows", + }, + heading: "Service 4", + tagline: "TAGLINE", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", + }, + ]) + .meta({ + description: "Two stacked service content sections on the right side.", + }), + +}); + +export type SchemaType = z.infer; + +const EducationServicesSplitSlide = ({ data }: { data: Partial }) => { + const { title, sections } = data; + + + return ( +
+
+
+

+ {title} +

+
+ + + +
+ {sections?.map((section, index) => ( +
+
+ + {section.serviceImage.__image_prompt__} +
+
+

{section.heading}

+

+ {section.tagline} +

+

+ {section.body} +

+
+
+ ))} +
+
+
+ ); +}; + +export default EducationServicesSplitSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx new file mode 100644 index 00000000..bd599daa --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx @@ -0,0 +1,153 @@ +import * as z from "zod"; + +export const slideLayoutId = "education-statistics-grid-slide"; +export const slideLayoutName = "Education Statistics Grid Slide"; +export const slideLayoutDescription = + "A two-column layout with a left title block and a right 2x4 grid of statistics cards, using one subtle background image texture."; + +const StatisticSchema = z.object({ + value: z.string().min(1).max(8).meta({ + description: "Main metric value shown at the top of one card.", + }), + label: z.string().min(3).max(22).meta({ + description: "Label shown under the value.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(4).max(14).default("Statistics").meta({ + description: "Main title shown in the left column.", + }), + description: z.string().min(40).max(120).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + ).meta({ + description: "Supporting line shown under the left title.", + }), + stats: z + .array(StatisticSchema) + .min(2) + .max(8) + .default([ + { value: "120", label: "Sales Team Strength" }, + { value: "15", label: "Senior Sales Officer" }, + { value: "1", label: "National Manager" }, + { value: "25", label: "Sales Officers" }, + { value: "2", label: "Regional Manager" }, + { value: "50", label: "Distributor Reps" }, + { value: "5", label: "Zonal Manager" }, + { value: "20", label: "Merchandising Team" }, + ]) + .meta({ + description: "Eight statistic cards shown in a 2-column, 4-row grid.", + }), + +}); + +export type SchemaType = z.infer; + + + +const EducationStatisticsGridSlide = ({ data }: { data: Partial }) => { + + + return ( +
+ + +
+
+
+

+ {data.title} +

+

+ {data.description} +

+
+
+ + {data.stats && data.stats?.length <= 4 &&
+ {data.stats?.map((stat, index) => ( +
+

+ {stat?.value} +

+

+ {stat?.label} +

+
+ ))} +
} + + {/* {stats && stats?.length > 4 && stats?.length <= 8 &&
+ {stats?.map((stat, index) => ( +
+

+ {stat.value} +

+

+ {stat.label} +

+
+ ))} +
} */} + + {data.stats && data.stats?.length > 4 && data.stats?.length <= 8 && (() => { + const rightArray = data.stats?.slice(0, Math.floor(data.stats?.length / 2)); + const leftArray = data.stats?.slice(Math.floor(data.stats?.length / 2)); + + return ( +
+
+ + {leftArray?.map((stat: any, index: number) => ( +
+

+ {stat?.value} +

+

+ {stat?.label} +

+
+ ))} +
+ + +
+ + {rightArray?.map((stat: any, index: number) => ( +
+

+ {stat.value} +

+

+ {stat.label} +

+
+ ))} +
+
+ ); + })()} +
+
+ ); +}; + +export default EducationStatisticsGridSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx new file mode 100644 index 00000000..e6e18552 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx @@ -0,0 +1,81 @@ +import * as z from "zod"; + + +export const slideLayoutId = "education-table-of-contents-slide"; +export const slideLayoutName = "Education Table Of Contents Slide"; +export const slideLayoutDescription = + "A split layout with a left title panel and a right list of numbered sections, with one subtle background image overlay."; + +const TocItemSchema = z.object({ + number: z.string().min(2).max(3).meta({ + description: "Section number displayed before each section title.", + }), + label: z.string().min(3).max(30).meta({ + description: "Section title shown in the right column list.", + }), +}); + +export const Schema = z.object({ + titleLine1: z.string().min(4).max(12).default("Table of").meta({ + description: "First line of the left-side heading.", + }), + titleLine2: z.string().min(4).max(12).default("Contents").meta({ + description: "Second line of the left-side heading.", + }), + items: z + .array(TocItemSchema) + .min(8) + .max(8) + .default([ + { number: "03", label: "ABOUT" }, + { number: "04", label: "TIMELINE" }, + { number: "05", label: "GROUP OF COMPANIES" }, + { number: "06", label: "SERVICES" }, + { number: "07", label: "IMAGE GALLERY" }, + { number: "08", label: "STATISTICS" }, + { number: "09", label: "REPORT" }, + { number: "10", label: "CONCLUSION" }, + ]) + .meta({ + description: "Eight table-of-content entries listed on the right.", + }), + +}); + +export type SchemaType = z.infer; + +const EducationTableOfContentsSlide = ({ data }: { data: Partial }) => { + + return ( +
+ + +
+
+

+ {data.titleLine1} +
+ {data.titleLine2} +

+
+ +
+
+ {data.items?.map((item, index) => ( +
+ + {item.number} + + + {item.label} + +
+ ))} +
+
+
+
+ ); +}; + +export default EducationTableOfContentsSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx new file mode 100644 index 00000000..0871cf62 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx @@ -0,0 +1,182 @@ +import * as z from "zod"; + +export const slideLayoutId = "education-timeline-slide"; +export const slideLayoutName = "Education Timeline Slide"; +export const slideLayoutDescription = + "A timeline slide with a title, a horizontal progress line, and year-based milestones with short descriptions."; + +const MilestoneSchema = z.object({ + year: z.string().min(4).max(6).meta({ + description: "Year label displayed under each timeline marker.", + }), + description: z.string().min(20).max(50).meta({ + description: "Short text shown under each year label.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(4).max(14).default("Timeline").meta({ + description: "Main timeline heading shown at the top-left.", + }), + milestones: z + .array(MilestoneSchema) + .min(6) + .max(12) + .default([ + { year: "2022", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1994", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1993", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1991", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1991", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " }, + { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + ]) + .meta({ + description: "Timeline milestones displayed left to right.", + }), +}); + +export type SchemaType = z.infer; + +const EducationTimelineSlide = ({ data }: { data: Partial }) => { + + const { title, milestones } = data; + + const isSixOrLess = milestones?.length && milestones?.length <= 6; + + return ( +
+
+

+ {title} +

+
+ + {isSixOrLess ? ( + + ) : ( + + )} +
+ ); +}; + +function TimelineUpToSix({ + milestones, +}: { + milestones: any; +}) { + + + return ( +
+
+ {milestones.map((milestone: any, index: number) => ( +
+
+ +
+ {index !== milestones.length - 1 &&
} +
+

+ {milestone.year} +

+

+ {milestone.description} +

+
+ ))} +
+ +
+ ); +} + +function TimelineMoreThanSix({ + milestones, +}: { + milestones: SchemaType["milestones"]; +}) { + const topItems = milestones.slice(0, 6); + const bottomItems = milestones.slice(6); + + return ( +
+ {/* vertical connector on left */} + + + + + + + {/* bottom horizontal line */} + {/*
*/} +
+
+ {topItems.map((milestone: any, index: number) => ( +
+
+ +
+ {index !== milestones.length - 1 &&
} +
+
+ +

+ {milestone.year} +

+

+ {milestone.description} +

+
+
+ ))} +
+ +
+ + {/* bottom row */} +
+ {bottomItems.map((_, colIndex) => { + const item = bottomItems[colIndex]; + if (!item) return
; + + return ( +
+
+ {/* {colIndex === 0 &&
} */} + {true &&
} +
+
+
+ +

+ {item.year} +

+

+ {item.description} +

+
+
+ ); + })} +
+
+ ); +} + +export default EducationTimelineSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/settings.json b/electron/servers/nextjs/app/presentation-templates/Education/settings.json new file mode 100644 index 00000000..42d837e2 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/settings.json @@ -0,0 +1,5 @@ +{ + "description": "School and training layouts for covers, outlines, timelines, statistics, and visual storytelling", + "ordered": false, + "default": false +} diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index 60131f0d..bbf1872b 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -16,6 +16,17 @@ import CodeSlide09TableOfContent, { Schema as CodeTableOfContentSchema, slideLay import CodeSlide10MetricsSplit, { Schema as CodeMetricsSplitSchema, slideLayoutId as CodeMetricsSplitId, slideLayoutName as CodeMetricsSplitName, slideLayoutDescription as CodeMetricsSplitDesc } from "./Code/CodeSlide10MetricsSplit"; import CodeSlide11MetricsGrid, { Schema as CodeMetricsGridSchema, slideLayoutId as CodeMetricsGridId, slideLayoutName as CodeMetricsGridName, slideLayoutDescription as CodeMetricsGridDesc } from "./Code/CodeSlide11MetricsGrid"; +// Education templates +import EducationCoverSlide, { Schema as EduCoverSchema, slideLayoutId as EduCoverId, slideLayoutName as EduCoverName, slideLayoutDescription as EduCoverDesc } from "./Education/EducationCoverSlide"; +import EducationTableOfContentsSlide, { Schema as EduTocSchema, slideLayoutId as EduTocId, slideLayoutName as EduTocName, slideLayoutDescription as EduTocDesc } from "./Education/EducationTableOfContentsSlide"; +import EducationAboutSlide, { Schema as EduAboutSchema, slideLayoutId as EduAboutId, slideLayoutName as EduAboutName, slideLayoutDescription as EduAboutDesc } from "./Education/EducationAboutSlide"; +import EducationContentSplitSlide, { Schema as EduContentSplitSchema, slideLayoutId as EduContentSplitId, slideLayoutName as EduContentSplitName, slideLayoutDescription as EduContentSplitDesc } from "./Education/EducationContentSplitSlide"; +import EducationImageGallerySlide, { Schema as EduImageGallerySchema, slideLayoutId as EduImageGalleryId, slideLayoutName as EduImageGalleryName, slideLayoutDescription as EduImageGalleryDesc } from "./Education/EducationImageGallerySlide"; +import EducationReportDonutSlide, { Schema as EduReportDonutSchema, slideLayoutId as EduReportDonutId, slideLayoutName as EduReportDonutName, slideLayoutDescription as EduReportDonutDesc } from "./Education/EducationReportDonutSlide"; +import EducationServicesSplitSlide, { Schema as EduServicesSplitSchema, slideLayoutId as EduServicesSplitId, slideLayoutName as EduServicesSplitName, slideLayoutDescription as EduServicesSplitDesc } from "./Education/EducationServicesSplitSlide"; +import EducationStatisticsGridSlide, { Schema as EduStatisticsGridSchema, slideLayoutId as EduStatisticsGridId, slideLayoutName as EduStatisticsGridName, slideLayoutDescription as EduStatisticsGridDesc } from "./Education/EducationStatisticsGridSlide"; +import EducationTimelineSlide, { Schema as EduTimelineSchema, slideLayoutId as EduTimelineId, slideLayoutName as EduTimelineName, slideLayoutDescription as EduTimelineDesc } from "./Education/EducationTimelineSlide"; + // 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"; @@ -189,6 +200,7 @@ import neoStandardSettings from "./neo-standard/settings.json"; import neoModernSettings from "./neo-modern/settings.json"; import neoSwiftSettings from "./neo-swift/settings.json"; import codeSettings from "./Code/settings.json"; +import educationSettings from "./Education/settings.json"; // Helper to create template entry @@ -210,6 +222,18 @@ export const codeTemplates: TemplateWithData[] = [ createTemplateEntry(CodeSlide11MetricsGrid, CodeMetricsGridSchema, CodeMetricsGridId, CodeMetricsGridName, CodeMetricsGridDesc, "code", "CodeSlide11MetricsGrid"), ]; +export const educationTemplates: TemplateWithData[] = [ + createTemplateEntry(EducationCoverSlide, EduCoverSchema, EduCoverId, EduCoverName, EduCoverDesc, "education", "EducationCoverSlide"), + createTemplateEntry(EducationTableOfContentsSlide, EduTocSchema, EduTocId, EduTocName, EduTocDesc, "education", "EducationTableOfContentsSlide"), + createTemplateEntry(EducationAboutSlide, EduAboutSchema, EduAboutId, EduAboutName, EduAboutDesc, "education", "EducationAboutSlide"), + createTemplateEntry(EducationContentSplitSlide, EduContentSplitSchema, EduContentSplitId, EduContentSplitName, EduContentSplitDesc, "education", "EducationContentSplitSlide"), + createTemplateEntry(EducationImageGallerySlide, EduImageGallerySchema, EduImageGalleryId, EduImageGalleryName, EduImageGalleryDesc, "education", "EducationImageGallerySlide"), + createTemplateEntry(EducationReportDonutSlide, EduReportDonutSchema, EduReportDonutId, EduReportDonutName, EduReportDonutDesc, "education", "EducationReportDonutSlide"), + createTemplateEntry(EducationServicesSplitSlide, EduServicesSplitSchema, EduServicesSplitId, EduServicesSplitName, EduServicesSplitDesc, "education", "EducationServicesSplitSlide"), + createTemplateEntry(EducationStatisticsGridSlide, EduStatisticsGridSchema, EduStatisticsGridId, EduStatisticsGridName, EduStatisticsGridDesc, "education", "EducationStatisticsGridSlide"), + createTemplateEntry(EducationTimelineSlide, EduTimelineSchema, EduTimelineId, EduTimelineName, EduTimelineDesc, "education", "EducationTimelineSlide"), +]; + export const neoGeneralTemplates: TemplateWithData[] = [ createTemplateEntry(TextSplitWithEmphasisBlockLayout, TextSplitWithEmphasisBlockSchema, TextSplitWithEmphasisBlockId, TextSplitWithEmphasisBlockName, TextSplitWithEmphasisBlockDesc, 'neo-general', 'TextSplitWithEmphasisBlock'), @@ -381,6 +405,7 @@ export const allLayouts: TemplateWithData[] = [ ...standardTemplates, ...swiftTemplates, ...codeTemplates, + ...educationTemplates, ]; @@ -450,6 +475,13 @@ export const templates: TemplateLayoutsWithSettings[] = [ settings: codeSettings as TemplateGroupSettings, layouts: codeTemplates, }, + { + id: "education", + name: "Education", + description: educationSettings.description, + settings: educationSettings as TemplateGroupSettings, + layouts: educationTemplates, + }, ]; // Helper to get templates by group ID From 604bb953867830af122de8f7a0ea656367364805 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Tue, 7 Apr 2026 12:40:39 +0545 Subject: [PATCH 03/18] feat: Product Overview template added --- .../BusinessChallengesCardsSlide.tsx | 133 +++++++++++++ .../BusinessChallengesGridSlide.tsx | 84 ++++++++ .../ProductOverview/ComparisonChartSlide.tsx | 187 ++++++++++++++++++ .../ComparisonTableWithTextSlide.tsx | 138 +++++++++++++ .../ProductOverview/CoverSlide.tsx | 94 +++++++++ .../ProductOverview/ImageGallerySlide.tsx | 139 +++++++++++++ .../ProductOverview/IntroductionSlide.tsx | 101 ++++++++++ .../ProductOverview/KpiCardsSlide.tsx | 114 +++++++++++ .../MarketOpportunitySlide.tsx | 108 ++++++++++ .../ProductOverview/MeetTeamSlide.tsx | 150 ++++++++++++++ .../ProductOverview/MissionVisionSlide.tsx | 88 +++++++++ .../ProductOverview/OurServicesSlide.tsx | 127 ++++++++++++ .../ProductOverview/PricingPlanSlide.tsx | 153 ++++++++++++++ .../ProductOverview/ProcessSlide.tsx | 173 ++++++++++++++++ .../ProductOverview/ReportSnapshotSlide.tsx | 162 +++++++++++++++ .../ProductOverview/TableOfContentSlide.tsx | 94 +++++++++ .../ProductOverview/settings.json | 5 + .../app/presentation-templates/index.tsx | 46 +++++ 18 files changed, 2096 insertions(+) create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/ProductOverview/settings.json diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx new file mode 100644 index 00000000..1258be58 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx @@ -0,0 +1,133 @@ +import * as z from "zod"; + + + +export const slideLayoutId = "product-overview-business-challenges-cards-slide"; +export const slideLayoutName = "Product Overview Business Challenges Cards Slide"; +export const slideLayoutDescription = + "A business challenges slide with headline and tagline on the left, a large supporting image in the top-right area, and three vertical detail cards across the lower center-right section."; + +const CardSchema = z.object({ + heading: z.string().min(4).max(12).meta({ + description: "Card heading for one challenge column.", + }), + body: z.string().min(30).max(60).meta({ + description: "Card body copy for one challenge column.", + }), + dark: z.boolean().default(false).meta({ + description: "Controls whether the card uses a dark emphasis style.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(24).default("Business Challenges").meta({ + description: "Main slide title.", + }), + taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ + description: "Short label above the left-side paragraph.", + }), + taglineBody: z.string().min(40).max(100).default( + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." + ).meta({ + description: "Supporting paragraph on the left side.", + }), + heroImage: z.object({ + __image_url__: z.string().url().default("https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1400&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Team meeting and stressed analyst"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1400&q=80", + __image_prompt__: "Team meeting and stressed analyst", + }).meta({ + description: "Primary image shown in the upper right area.", + }), + cards: z + .array(CardSchema) + .min(3) + .max(3) + .default([ + { + heading: "HEADING 1", + body: "Lorem ipsum dolor sit amet, consectetur elit.", + dark: false, + }, + { + heading: "HEADING 2", + body: "Lorem ipsum dolor sit amet, consectetur elit.", + dark: false, + }, + { + heading: "HEADING 3", + body: "Lorem ipsum dolor sit amet, consectetur elit.", + dark: true, + }, + ]) + .meta({ + description: "Three vertical challenge cards rendered under the image.", + }), +}); + +export type SchemaType = z.infer; + +const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) => { + const { title, taglineLabel, taglineBody, heroImage, cards } = data; + + return ( +
+
+

+ {title} +

+ +
+

+ {taglineLabel} +

+

{taglineBody}

+
+
+ + {heroImage?.__image_url__ && ( + {heroImage.__image_prompt__} + )} + +
+ {cards?.map((card, index) => ( +
+

+ {card.heading} +

+

+ {card.body} +

+
+ ))} +
+
+ ); +}; + +export default BusinessChallengesCardsSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx new file mode 100644 index 00000000..b6cdce83 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx @@ -0,0 +1,84 @@ +import * as z from "zod"; + +export const slideLayoutId = "product-overview-business-challenges-grid-slide"; +export const slideLayoutName = "Product Overview Business Challenges Grid Slide"; +export const slideLayoutDescription = + "A slide with a light title band on top and a dark content section below containing four challenge blocks in a two-by-two grid."; + +const ChallengeSchema = z.object({ + heading: z.string().min(4).max(12).meta({ + description: "Short heading for a single challenge block.", + }), + body: z.string().min(40).max(96).meta({ + description: "Description text for a single challenge block.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(24).default("Business Challenges").meta({ + description: "Main title shown in the top band.", + }), + challenges: z + .array(ChallengeSchema) + .min(2) + .max(4) + .default([ + { + heading: "HEADING 1", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.", + }, + { + heading: "HEADING 2", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.", + }, + { + heading: "HEADING 1", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.", + }, + { + heading: "HEADING 2", + body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.", + }, + ]) + .meta({ + description: "Four challenge blocks rendered in a 2x2 arrangement.", + }), +}); + +export type SchemaType = z.infer; + +const BusinessChallengesGridSlide = ({ data }: { data: Partial }) => { + const { title, challenges } = data; + + return ( +
+
+

+ {title} +

+
+ +
+ {challenges?.map((challenge, index) => ( +
+

+ {challenge.heading} +

+

{challenge.body}

+
+ ))} +
+
+ ); +}; + +export default BusinessChallengesGridSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx new file mode 100644 index 00000000..3a19af33 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -0,0 +1,187 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-comparison-chart-slide"; +export const slideLayoutName = "Product Overview Comparison Chart Slide"; +export const slideLayoutDescription = + "A comparison table slide with a headline, short description, four column headers, and three data rows using check, cross, or empty cells."; + +const CellStatusSchema = z.enum(["check", "cross", "empty"]); + +const RowSchema = z.object({ + label: z.string().min(4).max(18).meta({ + description: "Row heading shown in the first column.", + }), + cell1: CellStatusSchema.default("check"), + cell2: CellStatusSchema.default("empty"), + cell3: CellStatusSchema.default("check"), + cell4: CellStatusSchema.default("empty"), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(32).default("Comparison Chart").meta({ + description: "Main heading shown above the table.", + }), + subtitle: z.string().min(30).max(140).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt." + ).meta({ + description: "Short subtitle shown under the main heading.", + }), + columns: z + .array(z.string().min(4).max(18)) + .min(4) + .max(4) + .default(["HEADING 1", "HEADING 1", "HEADING 2", "HEADING 3"]) + .meta({ + description: "Four table column headings.", + }), + rows: z + .array(RowSchema) + .min(3) + .max(3) + .default([ + { + label: "HEADING 1", + cell1: "check", + cell2: "cross", + cell3: "check", + cell4: "cross", + }, + { + label: "HEADING 1", + cell1: "check", + cell2: "empty", + cell3: "check", + cell4: "empty", + }, + { + label: "HEADING 2", + cell1: "check", + cell2: "check", + cell3: "check", + cell4: "check", + }, + ]) + .meta({ + description: "Three table rows with status indicators.", + }), + checkIcon: z.object({ + __icon_url__: z.string(), + __icon_query__: z.string(), + }).default({ + __icon_url__: + "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }).meta({ + description: "Icon used for positive comparison status.", + }), + crossIcon: z.object({ + __icon_url__: z.string(), + __icon_query__: z.string(), + }).default({ + __icon_url__: + "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "cross icon", + }).meta({ + description: "Icon used for negative comparison status.", + }), +}); + +export type SchemaType = z.infer; + +function StatusIcon({ + status, + checkIconUrl, + checkIconAlt, + crossIconUrl, + crossIconAlt, +}: { + status: "check" | "cross" | "empty"; + checkIconUrl: string | undefined; + checkIconAlt: string | undefined; + crossIconUrl: string | undefined; + crossIconAlt: string | undefined; +}) { + if (status === "empty") { + return ; + } + + if (status === "cross") { + return {crossIconAlt}; + } + + return {checkIconAlt}; +} + +const ComparisonChartSlide = ({ data }: { data: Partial }) => { + const { title, subtitle, columns, rows, checkIcon, crossIcon } = data; + + return ( +
+
+

+ {title} +

+

{subtitle}

+
+ +
+
+
+ {columns?.map((column, index) => ( +
+ {column} +
+ ))} +
+ + {rows?.map((row, index) => { + const cells: ("check" | "cross" | "empty")[] = [ + row.cell1, + row.cell2, + row.cell3, + row.cell4, + ]; + + return ( +
+
+ {row.label} +
+ + {cells?.map((status, cellIndex) => ( +
+ +
+ ))} +
+ ); + })} +
+
+ ); +}; + +export default ComparisonChartSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx new file mode 100644 index 00000000..7475160b --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx @@ -0,0 +1,138 @@ +import * as z from "zod"; + +export const slideLayoutId = "product-overview-comparison-table-with-text-slide"; +export const slideLayoutName = "Product Overview Comparison Table With Text Slide"; +export const slideLayoutDescription = + "A comparison table slide with a title, a subtitle, four headers, and three text rows."; + +const RowSchema = z.object({ + cell1: z.string().min(8).max(24).meta({ + description: "First column cell text.", + }), + cell2: z.string().min(8).max(24).meta({ + description: "Second column cell text.", + }), + cell3: z.string().min(8).max(24).meta({ + description: "Third column cell text.", + }), + cell4: z.string().min(8).max(24).meta({ + description: "Fourth column cell text.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(32).default("Comparison Chart").meta({ + description: "Main heading shown above the table.", + }), + subtitle: z + .string() + .min(30) + .max(100) + .default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt." + ) + .meta({ + description: "Short subtitle shown under the main heading.", + }), + columns: z + .array(z.string().min(4).max(18)) + .min(4) + .max(4) + .default(["HEADING 1", "HEADING 1", "HEADING 2", "HEADING 3"]) + .meta({ + description: "Four table column headings.", + }), + highlightedHeaderIndex: z.number().int().min(1).max(4).default(4).meta({ + description: "1-based column index for the dark highlighted table header.", + }), + rows: z + .array(RowSchema) + .min(3) + .max(3) + .default([ + { + cell1: "Lorem ipsum dolor sit.", + cell2: "Lorem ipsum dolor sit.", + cell3: "Lorem ipsum dolor sit.", + cell4: "Lorem ipsum dolor sit.", + }, + { + cell1: "Lorem ipsum dolor sit.", + cell2: "Lorem ipsum dolor sit.", + cell3: "Lorem ipsum dolor sit.", + cell4: "Lorem ipsum dolor sit.", + }, + { + cell1: "Lorem ipsum dolor sit.", + cell2: "Lorem ipsum dolor sit.", + cell3: "Lorem ipsum dolor sit.", + cell4: "Lorem ipsum dolor sit.", + }, + ]) + .meta({ + description: "Three table rows of text content.", + }), +}); + +export type SchemaType = z.infer; + +const ComparisonTableWithTextSlide = ({ data }: { data: Partial }) => { + const { title, subtitle, columns, highlightedHeaderIndex, rows } = data; + + return ( +
+
+

+ {title} +

+

+ {subtitle} +

+
+ +
+ + + + {columns?.map((column, index) => { + const isHighlighted = index + 1 === highlightedHeaderIndex; + return ( + + ); + })} + + + + + {rows?.map((row, rowIndex) => { + const cells = [row.cell1, row.cell2, row.cell3, row.cell4]; + return ( + + {cells?.map((cell, cellIndex) => ( + + ))} + + ); + })} + +
+ {column} +
+ {cell} +
+
+
+ ); +}; + +export default ComparisonTableWithTextSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx new file mode 100644 index 00000000..2073ac26 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx @@ -0,0 +1,94 @@ +import * as z from "zod"; + + + +export const slideLayoutId = "product-overview-cover-slide"; +export const slideLayoutName = "Product Overview Cover Slide"; +export const slideLayoutDescription = + "A cover slide with a compact logo in the top-left, a date in the top-right, a two-line centered title, and a city/building image anchored to the bottom with a soft fade into the background."; + +export const Schema = z.object({ + logoImage: z.object({ + __image_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg"), + + __image_prompt__: z.string().min(10).max(100).default("Professional logo of the company"), + }).default({ + __image_url__: + "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg", + __image_prompt__: "Professional logo of the company", + }), + label: z.string().min(3).max(16).default("MARCH 2026").meta({ + description: "Date label shown at the top-right corner.", + }), + titleLine1: z.string().min(3).max(18).default("Social Media").meta({ + description: "First line of the cover title.", + }), + titleLine2: z.string().min(3).max(20).default("Marketing Report").meta({ + description: "Second line of the cover title.", + }), + backgroundImage: z.object({ + __image_url__: z.string().url().default("https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Tall glass buildings from street view"), + }).default({ + __image_url__: "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80", + __image_prompt__: "Tall glass buildings from street view", + }), +}); + +export type SchemaType = z.infer; + +const CoverSlide = ({ data }: { data: Partial }) => { + + const { logoImage, label, titleLine1, titleLine2, backgroundImage } = data; + + return ( +
+
+
+ + {logoImage?.__image_prompt__ + +

+ {label} +

+
+ +
+

+ {titleLine1} +
+ {titleLine2} +

+
+
+ + {backgroundImage?.__image_url__ && ( + {backgroundImage.__image_prompt__ + )} + +
+
+ ); +}; + +export default CoverSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx new file mode 100644 index 00000000..093d7e8e --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx @@ -0,0 +1,139 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-image-gallery-slide"; +export const slideLayoutName = "Product Overview Image Gallery Slide"; +export const slideLayoutDescription = + "A gallery slide with a title and paragraph on the left and a five-image collage on the right and bottom."; + +export const Schema = z.object({ + title: z.string().min(5).max(16).default("Image Gallery").meta({ + description: "Main gallery heading.", + }), + description: z.string().min(50).max(130).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." + ).meta({ + description: "Supporting paragraph shown under the title.", + }), + topCenterImage: z.object({ + __image_url__: + z.string().default("https://images.unsplash.com/photo-1521737711867-e3b97375f902?auto=format&fit=crop&w=800&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Design team discussing project board"), + }).default({ + __image_url__: "https://images.unsplash.com/photo-1521737711867-e3b97375f902?auto=format&fit=crop&w=800&q=80", + __image_prompt__: "Design team discussing project board", + }).meta({ + description: "Top-middle gallery image.", + }), + topRightImage: z.object({ + __image_url__: + z.string().default("https://images.unsplash.com/photo-1455390582262-044cdead277a?auto=format&fit=crop&w=800&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Creative desk with notebook and photos"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1455390582262-044cdead277a?auto=format&fit=crop&w=800&q=80", + __image_prompt__: "Creative desk with notebook and photos", + }).meta({ + description: "Top-right gallery image.", + }), + bottomWideImage: z.object({ + __image_url__: + z.string().default("https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1300&q=80"), + __image_prompt__: z.string().min(10).max(100).default("City skyline seen from office window"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1300&q=80", + __image_prompt__: "City skyline seen from office window", + }).meta({ + description: "Bottom-left wide gallery image.", + }), + bottomCenterImage: z.object({ + __image_url__: + z.string().default("https://images.unsplash.com/photo-1517048676732-d65bc937f952?auto=format&fit=crop&w=900&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Art gallery wall with framed photos"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1517048676732-d65bc937f952?auto=format&fit=crop&w=900&q=80", + __image_prompt__: "Art gallery wall with framed photos", + }).meta({ + description: "Bottom-center gallery image.", + }), + bottomRightImage: z.object({ + __image_url__: + z.string().default("https://images.unsplash.com/photo-1521791136064-7986c2920216?auto=format&fit=crop&w=900&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Office workshop with presentation board"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521791136064-7986c2920216?auto=format&fit=crop&w=900&q=80", + __image_prompt__: "Office workshop with presentation board", + }).meta({ + description: "Bottom-right gallery image.", + }), +}); + +export type SchemaType = z.infer; + +const ImageGallerySlide = ({ data }: { data: Partial }) => { + const { + title, + description, + topCenterImage, + topRightImage, + bottomWideImage, + bottomCenterImage, + bottomRightImage, + } = data; + + return ( +
+
+ +
+

+ {title} +

+

{description}

+
+
+ {topCenterImage?.__image_prompt__ + {topRightImage?.__image_prompt__ +
+
+ + +
+ {bottomWideImage?.__image_prompt__} + {bottomCenterImage?.__image_prompt__} + {bottomRightImage?.__image_prompt__} +
+
+ ); +}; + +export default ImageGallerySlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx new file mode 100644 index 00000000..f30cd3f2 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx @@ -0,0 +1,101 @@ +import * as z from "zod"; + + + +export const slideLayoutId = "product-overview-introduction-slide"; +export const slideLayoutName = "Product Overview Introduction Slide"; +export const slideLayoutDescription = + "A split slide with a large portrait image on the left and a structured introduction column on the right containing a main title and two labeled body paragraphs."; + +const IntroBlockSchema = z.object({ + label: z.string().min(3).max(12).meta({ + description: "Uppercase mini-heading shown above each intro paragraph.", + }), + body: z.string().min(40).max(96).meta({ + description: "Supporting paragraph content for the intro block.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(4).max(16).default("Introduction").meta({ + description: "Primary title in the right column.", + }), + portraitImage: z.object({ + __image_url__: z.string().url().default("https://images.unsplash.com/photo-1521119989659-a83eee488004?auto=format&fit=crop&w=1200&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Two business professionals in office"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521119989659-a83eee488004?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Two business professionals in office", + }).meta({ + description: "Main portrait image shown on the left half.", + }), + blocks: z + .array(IntroBlockSchema) + .min(2) + .max(2) + .default([ + { + label: "TAGLINE", + body: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea.", + }, + { + label: "TAGLINE", + body: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea.", + }, + ]) + .meta({ + description: "Two short intro content blocks shown in the text column.", + }), +}); + +export type SchemaType = z.infer; + +const IntroductionSlide = ({ data }: { data: Partial }) => { + const { title, portraitImage, blocks } = data; + + return ( +
+
+
+ + {portraitImage?.__image_url__ && ( + {portraitImage.__image_prompt__} + )} +
+ +
+

+ {title} +

+ +
+ {blocks?.map((block, index) => ( +
+

+ {block.label} +

+

+ {block.body} +

+
+ ))} +
+
+
+
+ ); +}; + +export default IntroductionSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx new file mode 100644 index 00000000..705803fb --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx @@ -0,0 +1,114 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-kpi-cards-slide"; +export const slideLayoutName = "Product Overview KPI Cards Slide"; +export const slideLayoutDescription = + "A KPI overview slide with a dark-tinted background image, large title, and a two-row grid of six white KPI cards."; + +const KpiSchema = z.object({ + value: z.string().min(1).max(8).meta({ + description: "Primary KPI value shown in a card.", + }), + body: z.string().min(10).max(28).meta({ + description: "Short KPI supporting text.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(3).max(10).default("KPIs").meta({ + description: "Main title shown in the top-left corner.", + }), + kpiIcon: z.object({ + __icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"), + __icon_query__: z.string().min(3).max(30).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 KPI card badge.", + }), + backgroundImage: z.object({ + __image_url__: z.string().url().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1600&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Business team using laptop in meeting"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1600&q=80", + __image_prompt__: "Business team using laptop in meeting", + }).meta({ + description: "Background image behind the KPI cards.", + }), + items: z + .array(KpiSchema) + .min(3) + .max(6) + .default([ + { value: "X 5", body: "Lorem ipsum dolor sit." }, + { value: "X 5", body: "Lorem ipsum dolor sit." }, + { value: "X 5", body: "Lorem ipsum dolor sit." }, + { value: "X 5", body: "Lorem ipsum dolor sit." }, + { value: "X 5", body: "Lorem ipsum dolor sit." }, + { value: "X 5", body: "Lorem ipsum dolor sit." }, + ]) + .meta({ + description: "Six KPI cards displayed in a 3x2 grid.", + }), +}); + +export type SchemaType = z.infer; + +const KpiCardsSlide = ({ data }: { data: Partial }) => { + const { title, kpiIcon, backgroundImage, items } = data; + + return ( +
+ {backgroundImage?.__image_url__ && ( + {backgroundImage?.__image_prompt__} + )} + +
+ +
+

+ {title} +

+
+ +
+ {items?.map((item, index) => ( +
+
+ {kpiIcon?.__icon_query__} +
+

+ {item.value} +

+

+ {item.body} +

+
+ ))} +
+
+ ); +}; + +export default KpiCardsSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx new file mode 100644 index 00000000..377c2b0e --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx @@ -0,0 +1,108 @@ +import * as z from "zod"; + +export const slideLayoutId = "product-overview-market-opportunity-slide"; +export const slideLayoutName = "Product Overview Market Opportunity Slide"; +export const slideLayoutDescription = + "A market opportunity slide with title and intro text on the left, four bullet lines extending toward the right, and concentric value circles as the visual focal point."; + +const BulletSchema = z.object({ + text: z.string().min(12).max(46).meta({ + description: "Bullet text shown on the left side of a line.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(22).default("Market Opportunity").meta({ + description: "Main heading shown at the top-left.", + }), + subtitle: z.string().min(40).max(110).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt." + ).meta({ + description: "Supporting text under the main heading.", + }), + bullets: z + .array(BulletSchema) + .min(4) + .max(4) + .default([ + { text: "Ut enim ad minim veniam, quis" }, + { text: "Ut enim ad minim veniam, quis" }, + { text: "Ut enim ad minim veniam, quis" }, + { text: "Ut enim ad minim veniam, quis" }, + ]) + .meta({ + description: "Four bullet-line entries shown on the left.", + }), + values: z + .array(z.string().min(2).max(6)) + .min(4) + .max(4) + .default(["$33", "$20", "$120", "$200"]) + .meta({ + description: "Four values shown from outer to inner circles.", + }), +}); + +export type SchemaType = z.infer; + + +const COLORS = [ + "#5f7f79", + "#1f5a4f", + "#0d4f43", + "#06463d", +]; + +const MarketOpportunitySlide = ({ data }: { data: Partial }) => { + const { title, subtitle, bullets, values } = data; + + return ( +
+
+

+ {title} +

+

{subtitle}

+
+ +
+ {bullets?.map((bullet, index) => ( +
+ +

{bullet.text}

+ + +
+ ))} +
+ +
+ {values?.map((value, index) => ( +
+

+ {value} +

+
+ ))} +
+
+ ); +}; + +export default MarketOpportunitySlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx new file mode 100644 index 00000000..5922c14d --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx @@ -0,0 +1,150 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-meet-team-slide"; +export const slideLayoutName = "Product Overview Meet Team Slide"; +export const slideLayoutDescription = + "A team introduction slide with a title and intro text on top, followed by four profile cards where one card can be highlighted with a dark footer style."; + +const MemberSchema = z.object({ + title: z.string().min(2).max(12).meta({ + description: "Member role or short heading.", + }), + name: z.string().min(2).max(16).meta({ + description: "Member name shown in the card footer.", + }), + image: z.object({ + __image_url__: z.string().url().default("https://i.pravatar.cc/600?img=12"), + __image_prompt__: z.string().min(10).max(100).default("Professional male portrait with suit"), + }).default({ + __image_url__: "https://i.pravatar.cc/600?img=12", + __image_prompt__: "Professional male portrait with suit", + }).meta({ + description: "Portrait image for a team member card.", + }), + highlighted: z.boolean().default(false).meta({ + description: "Whether this card uses the dark highlight footer.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(18).default("Meet Our Team").meta({ + description: "Main title at the top-left.", + }), + taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ + description: "Small heading above team description.", + }), + taglineBody: z.string().min(50).max(100).default( + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." + ).meta({ + description: "Short descriptive paragraph at top-right.", + }), + members: z + .array(MemberSchema) + .min(4) + .max(4) + .default([ + { + title: "CEO", + name: "Lanny LA", + image: { + __image_url__: "https://i.pravatar.cc/600?img=12", + __image_prompt__: "Professional male portrait with suit", + }, + highlighted: false, + }, + { + title: "HEADING 2", + name: "Lanny LA", + image: { + __image_url__: "https://i.pravatar.cc/600?img=13", + __image_prompt__: "Professional male portrait with tie", + }, + highlighted: false, + }, + { + title: "HEADING 3", + name: "Lanny LA", + image: { + __image_url__: "https://i.pravatar.cc/600?img=14", + __image_prompt__: "Professional male portrait smiling", + }, + highlighted: true, + }, + { + title: "HEADING 2", + name: "Lanny LA", + image: { + __image_url__: "https://i.pravatar.cc/600?img=15", + __image_prompt__: "Professional male portrait office", + }, + highlighted: false, + }, + ]) + .meta({ + description: "Four team member cards shown in one row.", + }), +}); + +export type SchemaType = z.infer; + +const MeetTeamSlide = ({ data }: { data: Partial }) => { + const { title, taglineLabel, taglineBody, members } = data; + + return ( +
+
+

+ {title} +

+ +
+

+ {taglineLabel} +

+

{taglineBody}

+
+
+ +
+ {members?.map((member, index) => ( +
+ {member.image.__image_prompt__} +
+

+ {member.title} +

+

+ {member.name} +

+
+
+ ))} +
+
+ ); +}; + +export default MeetTeamSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx new file mode 100644 index 00000000..7b94a574 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx @@ -0,0 +1,88 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-mission-vision-slide"; +export const slideLayoutName = "Product Overview Mission and Vision Slide"; +export const slideLayoutDescription = + "A quadrant layout with a large title in the top-left block, mission text in the top-right dark block, vision text in the bottom-left dark block, and an image in the bottom-right block."; + +export const Schema = z.object({ + title: z.string().min(8).max(20).default("Mission & Vision").meta({ + description: "Primary heading shown in the top-left tile.", + }), + + missionLabel: z.string().min(3).max(10).default("MISSION").meta({ + description: "Mission section label.", + }), + missionBody: z.string().min(40).max(98).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." + ).meta({ + description: "Mission paragraph content.", + }), + visionLabel: z.string().min(3).max(10).default("VISION").meta({ + description: "Vision section label.", + }), + visionBody: z.string().min(40).max(98).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." + ).meta({ + description: "Vision paragraph content.", + }), + image: z.object({ + __image_url__: z.string(), + __image_prompt__: z.string(), + }).optional().meta({ + description: "Bottom-right supporting image. Optional.", + }).default({ + __image_url__: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?auto=format&fit=crop&w=1400&q=80", + __image_prompt__: "Business silhouette at window skyline", + }), +}); + +export type SchemaType = z.infer; + +const MissionVisionSlide = ({ data }: { data: Partial }) => { + const { title, missionLabel, missionBody, visionLabel, visionBody, image } = data; + + return ( +
+
+
+

+ {title} +

+
+ +
+

+ {missionLabel} +

+

{missionBody}

+
+ +
+

+ {visionLabel} +

+

{visionBody}

+
+
+ + {image?.__image_url__ && ( + {image.__image_prompt__} + )} +
+
+
+ ); +}; + +export default MissionVisionSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx new file mode 100644 index 00000000..ce8067fa --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx @@ -0,0 +1,127 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-our-services-slide"; +export const slideLayoutName = "Product Overview Our Services Slide"; +export const slideLayoutDescription = + "A services slide with title and intro copy on the left, a large image below, and a two-by-two service card matrix on the right."; + +const ServiceSchema = z.object({ + heading: z.string().min(4).max(12).meta({ + description: "Service card heading.", + }), + body: z.string().min(20).max(44).meta({ + description: "Service card short description.", + }), + dark: z.boolean().default(false).meta({ + description: "Whether this service card uses the dark style.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Our Services").meta({ + description: "Main heading shown at the top-left.", + }), + taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ + description: "Small label above left paragraph.", + }), + taglineBody: z.string().min(40).max(100).default( + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." + ).meta({ + description: "Supporting text shown beneath the tagline label.", + }), + featureImage: z.object({ + __image_url__: z.string().url().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Customer support team in office"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Customer support team in office", + }).meta({ + description: "Main image shown at the lower left side.", + }), + services: z + .array(ServiceSchema) + .min(4) + .max(4) + .default([ + { heading: "HEADING 1", body: "Lorem ipsum dolor sit amet, consectetur", dark: false }, + { heading: "HEADING 2", body: "Lorem ipsum dolor sit amet, consectetur", dark: true }, + { heading: "HEADING 3", body: "Lorem ipsum dolor sit amet, consectetur", dark: false }, + { heading: "HEADING 4", body: "Lorem ipsum dolor sit amet, consectetur", dark: false }, + ]) + .meta({ + description: "Four service cards rendered on the right side.", + }), +}); + +export type SchemaType = z.infer; + +const OurServicesSlide = ({ data }: { data: Partial }) => { + const { title, taglineLabel, taglineBody, featureImage, services } = data; + + return ( +
+
+
+ +

+ {title} +

+ +
+

+ {taglineLabel} +

+

{taglineBody}

+
+
+
+ + {featureImage?.__image_url__ && ( + {featureImage?.__image_prompt__} + )} +
+
+ + + +
+ {services?.map((service, index) => ( +
+

+ {service.heading} +

+

+ {service.body} +

+
+ ))} +
+
+ ); +}; + +export default OurServicesSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx new file mode 100644 index 00000000..fd24e29a --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx @@ -0,0 +1,153 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-pricing-plan-slide"; +export const slideLayoutName = "Product Overview Pricing Plan Slide"; +export const slideLayoutDescription = + "A three-column pricing slide with one emphasized center plan and feature bullet lists for each plan."; + +const PlanSchema = z.object({ + price: z.string().min(4).max(12).meta({ + description: "Plan price label shown at the top of each card.", + }), + description: z.string().min(18).max(26).meta({ + description: "Short statement describing the plan.", + }), + features: z + .array(z.string().min(10).max(24)) + .min(4) + .max(4) + .meta({ + description: "Four bullet features shown in the pricing card.", + }), + highlighted: z.boolean().default(false).meta({ + description: "Whether this card uses the highlighted dark style.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Pricing Plan").meta({ + description: "Main slide title.", + }), + featureIcon: z.object({ + __icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"), + __icon_query__: z.string().min(3).max(30).default("check icon"), + }).default({ + __icon_url__: + "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "check icon", + }).meta({ + description: "Icon used for each feature bullet in plan cards.", + }), + plans: z + .array(PlanSchema) + .min(3) + .max(3) + .default([ + { + price: "$80/MONTH", + description: "Lorem ipsum dolor sit.", + features: [ + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + ], + highlighted: false, + }, + { + price: "$80/MONTH", + description: "Lorem ipsum dolor sit.", + features: [ + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + ], + highlighted: true, + }, + { + price: "$80/MONTH", + description: "Lorem ipsum dolor sit.", + features: [ + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + ], + highlighted: false, + }, + ]) + .meta({ + description: "Three pricing cards displayed across the slide.", + }), +}); + +export type SchemaType = z.infer; + +const PricingPlanSlide = ({ data }: { data: Partial }) => { + const { title, featureIcon, plans } = data; + + return ( +
+
+

+ {title} +

+
+ +
+ {plans?.map((plan, index) => { + const active = plan.highlighted; + return ( +
+

+ {plan.price} +

+

+ {plan.description} +

+ +
+ {plan.features.map((feature, featureIndex) => ( +
+ {featureIcon?.__icon_query__} +

+ {feature} +

+
+ ))} +
+
+ ); + })} +
+
+ ); +}; + +export default PricingPlanSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx new file mode 100644 index 00000000..8e57213f --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx @@ -0,0 +1,173 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-process-slide"; +export const slideLayoutName = "Product Overview Process Slide"; +export const slideLayoutDescription = + "A process diagram slide with five connected hexagon steps and alternating caption blocks above and below the flow."; + +const StepSchema = z.object({ + label: z.string().min(3).max(10).meta({ + description: "Short uppercase label for a process step.", + }), + body: z.string().max(20).meta({ + description: "Brief explanatory text for the process step.", + }), + icon: z.object({ + __icon_url__: z.string(), + __icon_query__: z.string(), + }).default({ + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "pulse icon", + }), + highlighted: z.boolean().default(false).meta({ + description: "Whether the hexagon is emphasized with dark fill.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(5).max(14).default("PROCESS").meta({ + description: "Main title shown in the top-left corner.", + }), + + steps: z + .array(StepSchema) + .min(5) + .max(5) + .default([ + { + label: "TAGLINE", body: "Ut enim ad minim.", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "pulse icon", + }, highlighted: false + }, + { + label: "TAGLINE", body: "Ut enim ad minim.", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "upload icon", + }, highlighted: false + }, + { + label: "TAGLINE", body: "Ut enim ad minim.", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "pulse icon", + }, highlighted: false + }, + { + label: "TAGLINE", body: "Ut enim ad minim.", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "upload icon", + }, highlighted: false + }, + { + label: "TAGLINE", body: "Ut enim ad minim.", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "pulse icon", + }, highlighted: true + }, + ]) + .meta({ + description: "Five process steps rendered from left to right.", + }), +}); + +export type SchemaType = z.infer; + + +const ProcessSlide = ({ data }: { data: Partial }) => { + const { title, steps } = data; + + return ( +
+
+

+ {title} +

+
+ + + +
+ {steps?.map((step, index) => { + if (index % 2 === 0) { + return ( +
+
+
+ {step.icon.__icon_query__} + + + +
+
+ + + +
+
+
+

+ {step.label} +

+

{step.body}

+
+
+ ) + } + else { + return ( +
+
+

+ {step.label} +

+

{step.body}

+
+ +
+
+ + + + {step.icon.__icon_query__} +
+
+ + + +
+
+
+ ) + } + })} +
+
+ ); +}; + +export default ProcessSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx new file mode 100644 index 00000000..bb0063ec --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx @@ -0,0 +1,162 @@ +import * as z from "zod"; + + +export const slideLayoutId = "product-overview-report-snapshot-slide"; +export const slideLayoutName = "Product Overview Report Snapshot Slide"; +export const slideLayoutDescription = + "A report summary slide with a left-edge photo strip, title and intro copy, a compact bar chart card, and a KPI callout card on the right."; + +const BarSchema = z.object({ + value: z.number().min(10).max(100).meta({ + description: "Relative bar value used in the spending mini chart.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(4).max(12).default("Report").meta({ + description: "Slide heading text.", + }), + taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ + description: "Small label above intro paragraph.", + }), + taglineBody: z.string().min(40).max(100).default( + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." + ).meta({ + description: "Intro paragraph shown beneath the heading.", + }), + sideImage: z.object({ + __image_url__: z.string().url().default("https://images.unsplash.com/photo-1520607162513-77705c0f0d4a?auto=format&fit=crop&w=700&q=80"), + __image_prompt__: z.string().min(10).max(100).default("Team members reviewing charts together"), + }).default({ + __image_url__: + "https://images.unsplash.com/photo-1520607162513-77705c0f0d4a?auto=format&fit=crop&w=700&q=80", + __image_prompt__: "Team members reviewing charts together", + }).meta({ + description: "Left-side vertical image strip.", + }), + chartTitle: z.string().min(3).max(20).default("Sandro Tavares").meta({ + description: "Name displayed in the chart card.", + }), + bars: z + .array(BarSchema) + .min(8) + .max(8) + .default([ + { value: 52 }, + { value: 24 }, + { value: 35 }, + { value: 48 }, + { value: 26 }, + { value: 72 }, + { value: 47 }, + { value: 55 }, + ]) + .meta({ + description: "Eight bars used in the spending card chart.", + }), + metricValue: z.string().min(1).max(8).default("X 5").meta({ + description: "KPI value in the callout card.", + }), + metricBody: z.string().min(10).max(18).default("Lorem ipsum.").meta({ + description: "KPI short text in the callout card.", + }), + metricIcon: z.object({ + __icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"), + __icon_query__: z.string().min(3).max(30).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 KPI callout card.", + }), +}); + +export type SchemaType = z.infer; + +const ReportSnapshotSlide = ({ data }: { data: Partial }) => { + const { + title, + taglineLabel, + taglineBody, + sideImage, + chartTitle, + bars, + metricValue, + metricBody, + metricIcon, + } = data; + + return ( +
+ {sideImage?.__image_url__ && ( + {sideImage?.__image_prompt__} + )} + +
+

+ {title} +

+ +
+

+ {taglineLabel} +

+

{taglineBody}

+
+
+ +
+

Spendings

+

+ {chartTitle} +

+ +
+ {bars?.map((bar, index) => ( +
+ ))} +
+ +
+

Current margin: April Spendings

+

$350.00 / $640.00

+
+
+ +
+
+
+ {metricIcon?.__icon_query__} +
+

+ {metricValue} +

+
+

+ {metricBody} +

+
+
+ ); +}; + +export default ReportSnapshotSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx new file mode 100644 index 00000000..5cdba0a3 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx @@ -0,0 +1,94 @@ +import * as z from "zod"; + +const PRODUCT_BG = "#d7dddd"; +const PRODUCT_DARK = "#05463d"; + + +export const slideLayoutId = "product-overview-table-of-content-slide"; +export const slideLayoutName = "Product Overview Table of Content Slide"; +export const slideLayoutDescription = + "A two-column table-of-content slide with section titles and numbers on a dark left panel and a large title plus description paragraph on the right panel."; + +const SectionSchema = z.object({ + title: z.string().min(4).max(14).meta({ + description: "Section label shown in the left navigation column.", + }), + number: z.string().min(2).max(3).meta({ + description: "Section number shown beside the section label.", + }), + description: z.string().min(4).max(22).optional().meta({ + description: "Section description shown in the right column.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Table Of Content").meta({ + description: "Heading in the right-side content area.", + }), + description: z.string().min(50).max(120).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." + ).meta({ + description: "Supporting descriptive paragraph under the heading.", + }), + sections: z + .array(SectionSchema) + .min(6) + .max(6) + .default([ + { title: "SECTION TITLE", number: "01", description: "Lorem ipsum dolor sit." }, + { title: "SECTION TITLE", number: "02", description: "Lorem ipsum dolor sit." }, + { title: "SECTION TITLE", number: "03", description: "Lorem ipsum dolor sit." }, + { title: "SECTION TITLE", number: "04", description: "Lorem ipsum dolor sit." }, + { title: "SECTION TITLE", number: "05", description: "Lorem ipsum dolor sit." }, + { title: "SECTION TITLE", number: "06", description: "Lorem ipsum dolor sit." }, + ]) + .meta({ + description: "Six rows listed in the table of contents panel.", + }), +}); + +export type SchemaType = z.infer; + +const TableOfContentSlide = ({ data }: { data: Partial }) => { + const { title, description, sections } = data; + + return ( +
+
+
+
+ {sections?.map((section, index) => ( +
+
+ +

+ {section.title} +

+

{section.description}

+
+

{section.number}

+
+ ))} +
+
+ +
+

+ {title} +

+

+ {description} +

+
+
+
+ ); +}; + +export default TableOfContentSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/settings.json b/electron/servers/nextjs/app/presentation-templates/ProductOverview/settings.json new file mode 100644 index 00000000..64702b29 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/settings.json @@ -0,0 +1,5 @@ +{ + "description": "Product pitch layouts for cover, narrative, comparisons, KPIs, pricing, and team slides", + "ordered": false, + "default": false +} diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index bbf1872b..c35ed584 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -27,6 +27,24 @@ import EducationServicesSplitSlide, { Schema as EduServicesSplitSchema, slideLay import EducationStatisticsGridSlide, { Schema as EduStatisticsGridSchema, slideLayoutId as EduStatisticsGridId, slideLayoutName as EduStatisticsGridName, slideLayoutDescription as EduStatisticsGridDesc } from "./Education/EducationStatisticsGridSlide"; import EducationTimelineSlide, { Schema as EduTimelineSchema, slideLayoutId as EduTimelineId, slideLayoutName as EduTimelineName, slideLayoutDescription as EduTimelineDesc } from "./Education/EducationTimelineSlide"; +// Product Overview templates +import BusinessChallengesCardsSlide, { Schema as PoBizChallengesCardsSchema, slideLayoutId as PoBizChallengesCardsId, slideLayoutName as PoBizChallengesCardsName, slideLayoutDescription as PoBizChallengesCardsDesc } from "./ProductOverview/BusinessChallengesCardsSlide"; +import BusinessChallengesGridSlide, { Schema as PoBizChallengesGridSchema, slideLayoutId as PoBizChallengesGridId, slideLayoutName as PoBizChallengesGridName, slideLayoutDescription as PoBizChallengesGridDesc } from "./ProductOverview/BusinessChallengesGridSlide"; +import ComparisonChartSlide, { Schema as PoComparisonChartSchema, slideLayoutId as PoComparisonChartId, slideLayoutName as PoComparisonChartName, slideLayoutDescription as PoComparisonChartDesc } from "./ProductOverview/ComparisonChartSlide"; +import ComparisonTableWithTextSlide, { Schema as PoComparisonTableSchema, slideLayoutId as PoComparisonTableId, slideLayoutName as PoComparisonTableName, slideLayoutDescription as PoComparisonTableDesc } from "./ProductOverview/ComparisonTableWithTextSlide"; +import CoverSlide, { Schema as PoCoverSchema, slideLayoutId as PoCoverId, slideLayoutName as PoCoverName, slideLayoutDescription as PoCoverDesc } from "./ProductOverview/CoverSlide"; +import ImageGallerySlide, { Schema as PoImageGallerySchema, slideLayoutId as PoImageGalleryId, slideLayoutName as PoImageGalleryName, slideLayoutDescription as PoImageGalleryDesc } from "./ProductOverview/ImageGallerySlide"; +import IntroductionSlide, { Schema as PoIntroductionSchema, slideLayoutId as PoIntroductionId, slideLayoutName as PoIntroductionName, slideLayoutDescription as PoIntroductionDesc } from "./ProductOverview/IntroductionSlide"; +import KpiCardsSlide, { Schema as PoKpiCardsSchema, slideLayoutId as PoKpiCardsId, slideLayoutName as PoKpiCardsName, slideLayoutDescription as PoKpiCardsDesc } from "./ProductOverview/KpiCardsSlide"; +import MarketOpportunitySlide, { Schema as PoMarketOpportunitySchema, slideLayoutId as PoMarketOpportunityId, slideLayoutName as PoMarketOpportunityName, slideLayoutDescription as PoMarketOpportunityDesc } from "./ProductOverview/MarketOpportunitySlide"; +import MeetTeamSlide, { Schema as PoMeetTeamSchema, slideLayoutId as PoMeetTeamId, slideLayoutName as PoMeetTeamName, slideLayoutDescription as PoMeetTeamDesc } from "./ProductOverview/MeetTeamSlide"; +import MissionVisionSlide, { Schema as PoMissionVisionSchema, slideLayoutId as PoMissionVisionId, slideLayoutName as PoMissionVisionName, slideLayoutDescription as PoMissionVisionDesc } from "./ProductOverview/MissionVisionSlide"; +import OurServicesSlide, { Schema as PoOurServicesSchema, slideLayoutId as PoOurServicesId, slideLayoutName as PoOurServicesName, slideLayoutDescription as PoOurServicesDesc } from "./ProductOverview/OurServicesSlide"; +import PricingPlanSlide, { Schema as PoPricingPlanSchema, slideLayoutId as PoPricingPlanId, slideLayoutName as PoPricingPlanName, slideLayoutDescription as PoPricingPlanDesc } from "./ProductOverview/PricingPlanSlide"; +import ProcessSlide, { Schema as PoProcessSchema, slideLayoutId as PoProcessId, slideLayoutName as PoProcessName, slideLayoutDescription as PoProcessDesc } from "./ProductOverview/ProcessSlide"; +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"; + // 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"; @@ -201,6 +219,7 @@ import neoModernSettings from "./neo-modern/settings.json"; 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"; // Helper to create template entry @@ -234,6 +253,25 @@ export const educationTemplates: TemplateWithData[] = [ createTemplateEntry(EducationTimelineSlide, EduTimelineSchema, EduTimelineId, EduTimelineName, EduTimelineDesc, "education", "EducationTimelineSlide"), ]; +export const productOverviewTemplates: TemplateWithData[] = [ + createTemplateEntry(CoverSlide, PoCoverSchema, PoCoverId, PoCoverName, PoCoverDesc, "product-overview", "CoverSlide"), + createTemplateEntry(TableOfContentSlide, PoTableOfContentSchema, PoTableOfContentId, PoTableOfContentName, PoTableOfContentDesc, "product-overview", "TableOfContentSlide"), + createTemplateEntry(IntroductionSlide, PoIntroductionSchema, PoIntroductionId, PoIntroductionName, PoIntroductionDesc, "product-overview", "IntroductionSlide"), + createTemplateEntry(MissionVisionSlide, PoMissionVisionSchema, PoMissionVisionId, PoMissionVisionName, PoMissionVisionDesc, "product-overview", "MissionVisionSlide"), + createTemplateEntry(MarketOpportunitySlide, PoMarketOpportunitySchema, PoMarketOpportunityId, PoMarketOpportunityName, PoMarketOpportunityDesc, "product-overview", "MarketOpportunitySlide"), + createTemplateEntry(BusinessChallengesGridSlide, PoBizChallengesGridSchema, PoBizChallengesGridId, PoBizChallengesGridName, PoBizChallengesGridDesc, "product-overview", "BusinessChallengesGridSlide"), + createTemplateEntry(BusinessChallengesCardsSlide, PoBizChallengesCardsSchema, PoBizChallengesCardsId, PoBizChallengesCardsName, PoBizChallengesCardsDesc, "product-overview", "BusinessChallengesCardsSlide"), + createTemplateEntry(OurServicesSlide, PoOurServicesSchema, PoOurServicesId, PoOurServicesName, PoOurServicesDesc, "product-overview", "OurServicesSlide"), + createTemplateEntry(ProcessSlide, PoProcessSchema, PoProcessId, PoProcessName, PoProcessDesc, "product-overview", "ProcessSlide"), + createTemplateEntry(ComparisonChartSlide, PoComparisonChartSchema, PoComparisonChartId, PoComparisonChartName, PoComparisonChartDesc, "product-overview", "ComparisonChartSlide"), + createTemplateEntry(ComparisonTableWithTextSlide, PoComparisonTableSchema, PoComparisonTableId, PoComparisonTableName, PoComparisonTableDesc, "product-overview", "ComparisonTableWithTextSlide"), + createTemplateEntry(KpiCardsSlide, PoKpiCardsSchema, PoKpiCardsId, PoKpiCardsName, PoKpiCardsDesc, "product-overview", "KpiCardsSlide"), + createTemplateEntry(ReportSnapshotSlide, PoReportSnapshotSchema, PoReportSnapshotId, PoReportSnapshotName, PoReportSnapshotDesc, "product-overview", "ReportSnapshotSlide"), + createTemplateEntry(PricingPlanSlide, PoPricingPlanSchema, PoPricingPlanId, PoPricingPlanName, PoPricingPlanDesc, "product-overview", "PricingPlanSlide"), + createTemplateEntry(MeetTeamSlide, PoMeetTeamSchema, PoMeetTeamId, PoMeetTeamName, PoMeetTeamDesc, "product-overview", "MeetTeamSlide"), + createTemplateEntry(ImageGallerySlide, PoImageGallerySchema, PoImageGalleryId, PoImageGalleryName, PoImageGalleryDesc, "product-overview", "ImageGallerySlide"), +]; + export const neoGeneralTemplates: TemplateWithData[] = [ createTemplateEntry(TextSplitWithEmphasisBlockLayout, TextSplitWithEmphasisBlockSchema, TextSplitWithEmphasisBlockId, TextSplitWithEmphasisBlockName, TextSplitWithEmphasisBlockDesc, 'neo-general', 'TextSplitWithEmphasisBlock'), @@ -406,6 +444,7 @@ export const allLayouts: TemplateWithData[] = [ ...swiftTemplates, ...codeTemplates, ...educationTemplates, + ...productOverviewTemplates, ]; @@ -482,6 +521,13 @@ export const templates: TemplateLayoutsWithSettings[] = [ settings: educationSettings as TemplateGroupSettings, layouts: educationTemplates, }, + { + id: "product-overview", + name: "Product Overview", + description: productOverviewSettings.description, + settings: productOverviewSettings as TemplateGroupSettings, + layouts: productOverviewTemplates, + }, ]; // Helper to get templates by group ID From 8f0b3b9e855892ba4da002ce6c2e1078d586f245 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Tue, 7 Apr 2026 12:58:18 +0545 Subject: [PATCH 04/18] feat: Report template added --- .../Report/DataAnalysisBarSlide.tsx | 133 +++++++ .../Report/DataAnalysisDashboardSlide.tsx | 350 ++++++++++++++++++ .../Report/DataAnalysisInsightBarSlide.tsx | 114 ++++++ .../Report/DataAnalysisLineStatsSlide.tsx | 185 +++++++++ .../Report/DataAnalysisListSlide.tsx | 93 +++++ .../Report/IntroSlide.tsx | 42 +++ .../Report/IntroductionImageSlide.tsx | 89 +++++ .../Report/IntroductionStatsSlide.tsx | 162 ++++++++ .../Report/MilestoneSlide.tsx | 119 ++++++ .../Report/PerformanceSnapshotSlide.tsx | 142 +++++++ .../Report/ServicesSlide.tsx | 209 +++++++++++ .../Report/SolutionSlide.tsx | 139 +++++++ .../Report/TeamSlide.tsx | 110 ++++++ .../Report/chartPrimitives.tsx | 338 +++++++++++++++++ .../Report/settings.json | 5 + .../app/presentation-templates/index.tsx | 40 ++ 16 files changed, 2270 insertions(+) create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/settings.json diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx new file mode 100644 index 00000000..6778d07e --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx @@ -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; + +const DataAnalysisBarSlide = ({ data }: { data: Partial }) => { + + const { title, itemIcon, items, chartData, legendLabel } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+
+ {items?.map((item, index) => ( +
+
+
+ {itemIcon?.__icon_query__} +
+

+ {item.title} +

+
+

+ {item.description} +

+
+ ))} +
+ +
+
+ +
+
+ +

{legendLabel}

+
+
+
+
+ ); +}; + +export default DataAnalysisBarSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx new file mode 100644 index 00000000..c5af4502 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx @@ -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; + +function SummaryCard({ + value, + label, + iconUrl, + iconAlt, +}: { + value: string; + label: string; + iconUrl?: string; + iconAlt?: string; +}) { + return ( +
+
+ {iconAlt +
+
+

+ {value} +

+

{label}

+
+
+ ); +} + +function ChartCell({ + children, + footer, + topLegend, +}: { + children: ReactNode; + footer?: ReactNode; + topLegend?: ReactNode; +}) { + return ( +
+ {topLegend &&
{topLegend}
} +
{children}
+ {footer &&
{footer}
} +
+ ); +} + +function DotLegend({ + items, +}: { + items: { label: string; color: string }[]; +}) { + return ( +
+ {items.map((item) => ( + + + {item.label} + + ))} +
+ ); +} + +const DataAnalysisDashboardSlide = ({ data }: { data: Partial }) => { + + const { title, summaryIcon, summaryCards, workflowBars, gaugeSegments, trendSeries, detailedArea, shareBreakdown, comparisonBars } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+ {summaryCards?.map((card, index) => ( + + ))} +
+ +
+
+ + } + > + + + + + } + > + + + + +

Category A

+

Category B

+
+ } + > + + +
+ +
+ + } + > + + + + + } + > + + + + + } + > + + +
+
+
+ ); +}; + +export default DataAnalysisDashboardSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx new file mode 100644 index 00000000..bb4839a1 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx @@ -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; + +const DataAnalysisInsightBarSlide = ({ + data, +}: { + data: Partial; +}) => { + + const { title, insightIcon, insightBody, chartData, legendLabel } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+
+
+
+ {insightIcon?.__icon_query__} +
+
+

+ {insightBody} +

+
+ +
+
+ +
+
+ +

{legendLabel}

+
+
+
+
+ ); +}; + +export default DataAnalysisInsightBarSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx new file mode 100644 index 00000000..c19cb3d7 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx @@ -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; + +type StatMetric = { + value: string; + label: string; + description: string; +}; + +function StatPill({ + metrics, + +}: { + metrics: StatMetric[]; + +}) { + + + return ( +
+ + {metrics.map((metric, index) => ( + <> +
+

+ {metric.value} +

+

{metric.label}

+

+ {metric.description} +

+
+ {index === 0 &&
+ + + + +
+ } + + ))} + + +
+ ); +} +const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => { + const { title, seriesALabel, seriesBLabel, lineData, statColumns } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+
+
+ + + {seriesALabel} + + + + {seriesBLabel} + +
+ +
+ +
+ +
+ X axis name +
+
+ +
+ {statColumns?.map((column, index) => ( + + ))} +
+
+
+ ); +}; + +export default DataAnalysisLineStatsSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx new file mode 100644 index 00000000..60c07657 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx @@ -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; + +const DataAnalysisListSlide = ({ data }: { data: Partial }) => { + const { title, itemIcon, items } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+ {items?.map((item, index) => ( +
+
+
+ {itemIcon?.__icon_query__} +
+

+ {item.title} +

+
+

+ {item.description} +

+
+ ))} +
+
+ ); +}; + +export default DataAnalysisListSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx new file mode 100644 index 00000000..d84fa166 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx @@ -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; +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 }) => { + const { title, subtitle, name, position } = data; + + return ( +
+ + + + + + + + + +
+

{title}

+

{subtitle}

+
+
+
+

{name}

+

{position}

+
+ +
+ ) +} + +export default IntroSlide diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx new file mode 100644 index 00000000..b9529372 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx @@ -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; + +const IntroductionImageSlide = ({ data }: { data: Partial }) => { + const { title, body, bullets, featureImage } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+
+

+ {body} +

+ +
    + {bullets?.map((bullet, index) => ( +
  • + {bullet} +
  • + ))} +
+
+ +
+
+ {featureImage?.__image_prompt__} +
+
+
+
+ ); +}; + +export default IntroductionImageSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx new file mode 100644 index 00000000..f938a485 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx @@ -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; + +type StatMetric = { + value: string; + label: string; + description: string; +}; + +function StatPill({ + metrics, + +}: { + metrics: StatMetric[]; + +}) { + + + return ( +
+ + {metrics.map((metric, index) => ( + <> +
+

+ {metric.value} +

+

{metric.label}

+

+ {metric.description} +

+
+ {index === 0 &&
+ + + + +
+ } + + ))} + + +
+ ); +} + +const IntroductionStatsSlide = ({ data }: { data: Partial }) => { + + const { title, body, bullets, statColumns } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+
+

+ {body} +

+ +
    + {bullets?.map((bullet, index) => ( +
  • + {bullet} +
  • + ))} +
+
+ +
+ {statColumns?.map((column, index) => ( + + ))} +
+
+
+ ); +}; + +export default IntroductionStatsSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx new file mode 100644 index 00000000..5db5a866 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx @@ -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; + +const MilestoneSlide = ({ data }: { data: Partial }) => { + const { title, activeIndex, items } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+
+ {items?.map((item, index) => { + const isActive = index === activeIndex; + + return ( +
+ +
0 ? "ml-[-45px]" : ""} `} + > + + {item.stepNumber} + + +
+
0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`}> +

+ {item.heading} +

+

+ {item.description} +

+
+
+ ); + })} +
+ + +
+
+ ); +}; + +export default MilestoneSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx new file mode 100644 index 00000000..eea9d05e --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx @@ -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; + +type StatMetric = { + value: string; + label: string; + description: string; +}; + +function StatPill({ + metrics, + +}: { + metrics: StatMetric[]; + +}) { + + + return ( +
+ + {metrics.map((metric, index) => ( + <> +
+

+ {metric.value} +

+

{metric.label}

+

+ {metric.description} +

+
+ {index === 0 &&
+ + + + +
+ } + + ))} + + +
+ ); +} + +const PerformanceSnapshotSlide = ({ data }: { data: Partial }) => { + const { title, columns } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+ {columns?.map((column, index) => ( + + ))} +
+
+ ); +}; + +export default PerformanceSnapshotSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx new file mode 100644 index 00000000..497eebf1 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx @@ -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; + +function ServiceGlyph({ + iconUrl, + iconAlt, + isActive, +}: { + iconUrl: string; + iconAlt: string; + isActive: boolean; +}) { + return ( + {iconAlt} + ); +} + +const ServicesSlide = ({ data }: { data: Partial }) => { + + const { title, activeIndex, items } = data; + + return ( +
+
+ +
+

+ {title} +

+
+ +
+ {items?.map((item, index) => { + const isActive = index === activeIndex; + + return ( + +
+
+ +
+ +

+ {item.heading} +

+

+ {item.description} +

+
+ + {index < items?.length - 1 && ( +
+ {/*
+ + + */} + + + +
+ )} + + ); + })} +
+
+ ); +}; + +export default ServicesSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx new file mode 100644 index 00000000..e724d4a9 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx @@ -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; + +type SolutionSlideProps = { + data: Partial; +}; + +function SolutionCard({ + stepNumber, + description, +}: { + stepNumber: string; + description: string; +}) { + return ( +
+

{stepNumber}

+

+ {description} +

+
+ ); +} + +const SolutionSlide = ({ data }: SolutionSlideProps) => { + const { title, showImage, featureImage, cards } = data; + const visibleCards = showImage ? cards?.slice(0, 2) : cards; + + return ( +
+
+ +
+ {title && ( +

+ {title} +

+ )} + +
+ {showImage ? ( +
+ {featureImage?.__image_url__ && ( +
+ {featureImage?.__image_prompt__} +
+ )} + +
+ {visibleCards?.map((card, index) => ( + + ))} +
+
+ ) : ( +
+ {visibleCards?.map((card, index) => ( + + ))} +
+ )} +
+
+
+ ); +}; + +export default SolutionSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx new file mode 100644 index 00000000..0e4b62f3 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx @@ -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; + +const TeamSlide = ({ data }: { data: Partial }) => { + + return ( +
+
+ {data?.members?.map((member, index) => ( +
+ {member.image.__image_prompt__} +
+
+

{member.title}

+

+ {member.name} +

+
+
+ ))} +
+
+ ); +}; + +export default TeamSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx b/electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx new file mode 100644 index 00000000..87b7b82f --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx @@ -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 ( + cx ? "start" : "end"} + dominantBaseline="central" + fontSize="10" + fontWeight="500" + > + {(percent * 100).toFixed(1)}% + + ); +} + +export function CompactBarChart({ + data, +}: { + data: SimpleBarDatum[]; +}) { + return ( + + + + + + + + + + + ); +} + +export function WorkflowBarChart({ + data, +}: { + data: SimpleBarDatum[]; +}) { + return ( + + + + + + + + + + + ); +} + +export function TrendLineChart({ + data, +}: { + data: DualSeriesDatum[]; +}) { + return ( + + + + + + + + + + ); +} + +export function DualLineChart({ + data, +}: { + data: DualSeriesDatum[]; +}) { + return ( + + + + + + + + + + ); +} + +export function AreaTrendChart({ + data, + idPrefix, +}: { + data: SimpleBarDatum[]; + idPrefix: string; +}) { + return ( + + + + + + + + + + + + + + + ); +} + +export function SemiDonutChart({ + data, +}: { + data: PieDatum[]; +}) { + return ( + + + + {data.map((entry, index) => ( + + ))} + + + + ); +} + +export function CompactPieChart({ + data, +}: { + data: PieDatum[]; +}) { + return ( + + + + {data.map((entry, index) => ( + + ))} + + + + ); +} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/settings.json b/electron/servers/nextjs/app/presentation-templates/Report/settings.json new file mode 100644 index 00000000..d8f8abde --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/settings.json @@ -0,0 +1,5 @@ +{ + "description": "Data and narrative report layouts for intros, analysis charts, dashboards, and closing slides", + "ordered": false, + "default": false +} diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index c35ed584..e35e7e0c 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -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 From a485342f3cf2b4a5f9dd9acde8ce940bf8f44a6a Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Wed, 8 Apr 2026 21:12:32 +0545 Subject: [PATCH 05/18] feat: New templates added & improvements --- .../Code/CodeSlide01RoadmapCover.tsx | 4 +- .../Code/CodeSlide02CodeExplanationSplit.tsx | 2 +- .../Code/CodeSlide03ApiRequestResponse.tsx | 2 +- .../Code/CodeSlide04FeatureGrid.tsx | 10 +- .../Code/CodeSlide05ComparisonTable.tsx | 54 +- .../Code/CodeSlide06Workflow.tsx | 10 +- .../Code/CodeSlide07UseCaseList.tsx | 4 +- .../Code/CodeSlide08CodeExplanationText.tsx | 4 +- .../Code/CodeSlide10MetricsSplit.tsx | 3 +- .../Code/CodeSlide11MetricsGrid.tsx | 2 +- .../Education/EducationChartPrimitives.tsx | 511 +++++++++++ .../Education/EducationReportChartSlide.tsx | 180 ++++ .../Education/EducationReportDonutSlide.tsx | 130 --- .../ProductOverview/ReportSnapshotSlide.tsx | 556 ++++++++++-- .../Report/DataAnalysisBarSlide.tsx | 94 +- .../Report/DataAnalysisDashboardSlide.tsx | 412 ++++----- .../Report/DataAnalysisInsightBarSlide.tsx | 117 ++- .../Report/DataAnalysisLineStatsSlide.tsx | 112 +-- .../Report/DataAnalysisListSlide.tsx | 8 +- .../Report/IntroSlide.tsx | 8 +- .../Report/IntroductionImageSlide.tsx | 8 +- .../Report/IntroductionStatsSlide.tsx | 21 +- .../Report/MilestoneSlide.tsx | 6 +- .../Report/PerformanceSnapshotSlide.tsx | 13 +- .../Report/ServicesSlide.tsx | 4 +- .../Report/SolutionSlide.tsx | 4 +- .../Report/TeamSlide.tsx | 6 +- .../Report/chartPrimitives.tsx | 338 ------- .../Report/flexibleReportChart.tsx | 834 ++++++++++++++++++ .../app/presentation-templates/index.tsx | 2 +- 30 files changed, 2404 insertions(+), 1055 deletions(-) create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx delete mode 100644 electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx delete mode 100644 electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx create mode 100644 electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx index 52f779b8..4eefd0ee 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx @@ -6,7 +6,7 @@ export const slideLayoutDescription = "A centered opening slide with company name, roadmap title, and supporting subtitle."; export const Schema = z.object({ - companyName: z.string().min(2).max(28).default("COMPANY NAME").meta({ + companyName: z.string().min(2).max(18).default("COMPANY NAME").meta({ description: "Organization name shown above the slide title.", }), title: z.string().min(8).max(28).default("Development Roadmap").meta({ @@ -15,7 +15,7 @@ export const Schema = z.object({ subtitle: z .string() .min(24) - .max(92) + .max(40) .default( "We transform ideas into market-ready solutions through systematic development processes." ) diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx index fdee3e06..7edb0a22 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx @@ -45,7 +45,7 @@ export function UserAuth() { .min(40) .max(360) .default( - "This component manages credentials as local state and submits them through an async handler. The login utility abstracts network details while the handler keeps the UI flow predictable. Keep validation and side effects isolated so changes remain safe when authentication requirements evolve." + "This component manages credentials as local state and submits them through an async handler. The login utility abstracts network details while the handler keeps the UI flow predictable. Keep validation and side effects isolated so changes remain safe when authentication requirements evolve. " ) .meta({ description: "Explanation paragraph shown in the right column.", diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx index 42e9f206..9fe00314 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx @@ -17,7 +17,7 @@ export const Schema = z.object({ description: "Endpoint path text.", }), headers: z - .array(z.string().min(12).max(44)) + .array(z.string().max(10)) .min(2) .max(2) .default(["Content-Type: application/json", "Authorization: Bearer "]) diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx index b7700234..950b2dc6 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx @@ -1,17 +1,17 @@ import * as z from "zod"; const FeatureCardSchema = z.object({ - title: z.string().min(3).max(20).meta({ + title: z.string().min(3).max(17).meta({ description: "Feature title shown on each card.", }), - description: z.string().min(18).max(82).meta({ + description: z.string().min(18).max(80).meta({ description: "Supporting feature description.", }), icon: z.object({ - __icon_url__: z.string().min(10).max(180).meta({ + __icon_url__: z.string().meta({ description: "URL to icon", }), - __icon_query__: z.string().min(3).max(28).meta({ + __icon_query__: z.string().meta({ description: "Query used to search the icon", }), }).default({ @@ -45,7 +45,7 @@ export const Schema = z.object({ }, }, { - title: "Component Library", + title: "Component Library ", description: "Reusable UI components with consistent design patterns.", icon: { __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx index f76a12c0..2ff35020 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx @@ -1,17 +1,17 @@ import * as z from "zod"; const ComparisonRowSchema = z.object({ - feature: z.string().min(4).max(20).meta({ + feature: z.string().min(4).max(17).meta({ description: "Feature label shown in the first column.", }), - react: z.string().min(1).max(12).meta({ - description: "React cell value.", + column1: z.string().max(10).meta({ + description: "Column 1 cell value.", }), - vue: z.string().min(1).max(12).meta({ - description: "Vue cell value.", + column2: z.string().max(10).meta({ + description: "Column 2 cell value.", }), - angular: z.string().min(1).max(12).meta({ - description: "Angular cell value.", + column3: z.string().max(10).meta({ + description: "Column 3 cell value.", }), }); @@ -24,17 +24,20 @@ export const Schema = z.object({ title: z.string().min(6).max(18).default("Comparison").meta({ description: "Slide title shown above the table.", }), + tableColumns: z.array(z.string().max(4)).meta({ + description: "Table columns shown in the first row.", + }).default(["Feature", "Column 1", "Column 2", "Column 3"]), rows: z .array(ComparisonRowSchema) .min(6) .max(6) .default([ - { feature: "Component-based", react: "check", vue: "check", angular: "check" }, - { feature: "TypeScript Support", react: "check", vue: "check", angular: "check" }, - { feature: "Learning Curve", react: "Medium", vue: "Easy", angular: "Steep" }, - { feature: "Bundle Size", react: "40KB", vue: "34KB", angular: "167KB" }, - { feature: "Performance", react: "Excellent", vue: "Excellent", angular: "Good" }, - { feature: "Community Size", react: "Huge", vue: "Large", angular: "Large" }, + { feature: "Component-based", column1: "check", column2: "check", column3: "check" }, + { feature: "TypeScript Support", column1: "check", column2: "check", column3: "check" }, + { feature: "Learning Curve", column1: "Medium", column2: "Easy", column3: "Steep" }, + { feature: "Bundle Size", column1: "40KB", column2: "34KB", column3: "167KB" }, + { feature: "Performance", column1: "Excellent", column2: "Excellent", column3: "Good" }, + { feature: "Community Size", column1: "Huge", column2: "Large", column3: "Large" }, ]) .meta({ description: "Six comparison rows shown in the table.", @@ -62,23 +65,30 @@ const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) =>

{data.title}

-
-

Feature

-

React

-

Vue

-

Angular

+
+ + {data?.tableColumns?.map((column) => ( +

{column}

+ ))}
{data?.rows?.map((row) => (

{row.feature}

-
{renderCell(row.react)}
-
{renderCell(row.vue)}
-
{renderCell(row.angular)}
+
{renderCell(row.column1)}
+
{renderCell(row.column2)}
+
{renderCell(row.column3)}
))}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx index 15ad1f1a..f573e69e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx @@ -5,12 +5,16 @@ const WorkflowStepSchema = z.object({ title: z.string().min(3).max(12).meta({ description: "Step title shown in each workflow card.", }), - description: z.string().min(18).max(58).meta({ + description: z.string().min(18).max(50).meta({ description: "Short step description text.", }), icon: z.object({ - __icon_url__: z.string().min(10).max(180), - __icon_query__: z.string().min(3).max(28), + __icon_url__: z.string().meta({ + description: "URL to icon", + }), + __icon_query__: z.string().meta({ + description: "Query used to search the icon", + }), }).default({ __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", __icon_query__: "check icon", diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx index 69013541..2d44f2a5 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx @@ -6,11 +6,11 @@ export const slideLayoutDescription = "A two-column numbered use-case list with eight compact items."; export const Schema = z.object({ - title: z.string().min(6).max(16).default("Usecase").meta({ + title: z.string().min(6).max(30).default("Usecase").meta({ description: "Slide title shown above the numbered list.", }), items: z - .array(z.string().min(16).max(58)) + .array(z.string().min(4).max(8)) .min(4) .max(8) .default([ diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx index 0cf80abb..b22c127f 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx @@ -6,7 +6,7 @@ export const slideLayoutDescription = "A text-only explanation slide with generous whitespace for narrative documentation."; export const Schema = z.object({ - title: z.string().min(8).max(24).default("Code + Explanation").meta({ + title: z.string().min(8).max(30).default("Code + Explanation").meta({ description: "Main slide title shown at the top-left.", }), explanationTitle: z.string().min(4).max(20).default("Explanation").meta({ @@ -14,7 +14,7 @@ export const Schema = z.object({ }), explanation: z .string() - .min(60) + .max(360) .default( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx index 66569317..666ba65c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx @@ -1,7 +1,7 @@ import * as z from "zod"; const MetricSchema = z.object({ - value: z.string().min(2).max(8).meta({ + value: z.string().min(2).max(6).meta({ description: "Primary metric value.", }), label: z.string().min(3).max(14).meta({ @@ -26,7 +26,6 @@ export const Schema = z.object({ }), explanation: z .string() - .min(60) .max(320) .default( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx index 6dd3ea05..ac0d5659 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx @@ -1,7 +1,7 @@ import * as z from "zod"; const MetricSchema = z.object({ - value: z.string().min(2).max(8).meta({ + value: z.string().min(2).max(6).meta({ description: "Primary metric value.", }), label: z.string().min(3).max(14).meta({ diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx new file mode 100644 index 00000000..a27e2a44 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx @@ -0,0 +1,511 @@ +"use client"; + +import { + Area, + AreaChart, + Bar, + BarChart, + CartesianGrid, + Cell, + LabelList, + Legend, + Line, + LineChart, + Pie, + PieChart, + ReferenceLine, + ResponsiveContainer, + Scatter, + ScatterChart, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +export type EducationChartType = + | "bar" + | "bar-horizontal" + | "bar-grouped-vertical" + | "bar-grouped-horizontal" + | "bar-stacked-vertical" + | "bar-stacked-horizontal" + | "bar-clustered" + | "bar-diverging" + | "line" + | "area" + | "area-stacked" + | "pie" + | "donut" + | "scatter"; + +export type SimpleDatum = { + name: string; + value: number; +}; + +export type MultiSeriesDatum = { + name: string; + values: Record; +}; + +export type DivergingDatum = { + name: string; + positive: number; + negative: number; +}; + +export type ScatterDatum = { + x: number; + y: number; + name?: string; +}; + +export type EducationChartDatum = SimpleDatum | MultiSeriesDatum | DivergingDatum | ScatterDatum; + +type TooltipPayloadItem = { + color?: string; + dataKey?: string | number; + name?: string; + value?: string | number; +}; + +type TooltipProps = { + active?: boolean; + label?: string | number; + payload?: TooltipPayloadItem[]; +}; + +type PieLabelProps = { + name?: string; + percent?: number; + textAnchor?: "start" | "middle" | "end"; + x?: number; + y?: number; +}; + +const DEFAULT_COLORS = [ + "#4A15A8", + "#5B45AD", + "#7E6CC0", + "#9F94CD", + "#6A31B8", + "#4D2A97", +]; + +const AXIS = "#7C7A83"; +const GRID = "#CFCBD8"; +const PRIMARY_TEXT = "#3E3C45"; + +function formatComma(value: number | string) { + if (typeof value === "number") { + return value.toLocaleString("en-US"); + } + + const parsed = Number(value); + if (Number.isFinite(parsed)) { + return parsed.toLocaleString("en-US"); + } + + return value; +} + +function isSimpleDatum(item: EducationChartDatum): item is SimpleDatum { + return typeof (item as SimpleDatum).name === "string" && typeof (item as SimpleDatum).value === "number"; +} + +function isMultiSeriesDatum(item: EducationChartDatum): item is MultiSeriesDatum { + return ( + typeof (item as MultiSeriesDatum).name === "string" && + typeof (item as MultiSeriesDatum).values === "object" && + (item as MultiSeriesDatum).values !== null + ); +} + +function isDivergingDatum(item: EducationChartDatum): item is DivergingDatum { + return ( + typeof (item as DivergingDatum).name === "string" && + typeof (item as DivergingDatum).positive === "number" && + typeof (item as DivergingDatum).negative === "number" + ); +} + +function isScatterDatum(item: EducationChartDatum): item is ScatterDatum { + return typeof (item as ScatterDatum).x === "number" && typeof (item as ScatterDatum).y === "number"; +} + +function toSimpleData(data: EducationChartDatum[]) { + return data + .filter(isSimpleDatum) + .map((item) => ({ + name: item.name, + value: item.value, + })); +} + +function toScatterData(data: EducationChartDatum[]) { + const scatterData = data.filter(isScatterDatum); + + if (scatterData.length > 0) { + return scatterData.map((item, index) => ({ + x: item.x, + y: item.y, + name: item.name ?? String(index + 1), + })); + } + + return data + .filter(isSimpleDatum) + .map((item, index) => ({ + x: index + 1, + y: item.value, + name: item.name, + })); +} + +const renderPieInsideLabel = (props: any) => { + const { + cx = 0, + cy = 0, + midAngle = 0, + innerRadius: ir = 0, + outerRadius: or = 0, + percent = 0, + name, + } = props; + if (percent < 0.08) return null; + const toNum = (v: unknown) => { + if (typeof v === "number" && Number.isFinite(v)) return v; + if (typeof v === "string" && v.trim().endsWith("%")) return NaN; + const n = Number(v); + return Number.isFinite(n) ? n : NaN; + }; + let inner = toNum(ir); + let outer = toNum(or); + if (!Number.isFinite(outer)) { + outer = 140; + inner = Number.isFinite(inner) ? inner : 0; + } + if (!Number.isFinite(inner)) inner = 0; + const midR = inner + (outer - inner) * 0.5; + const rad = (-midAngle * Math.PI) / 180; + const x = cx + midR * Math.cos(rad); + const y = cy + midR * Math.sin(rad); + const nm = String(name ?? ""); + const short = nm.length <= 10; + const pct = `${(percent * 100).toFixed(0)}%`; + const fontSize = short ? 10 : 9; + const labelText = short ? `${name} ${pct}` : pct; + return ( + + {labelText} + + ); +}; + +function transformMultiSeriesData(data: EducationChartDatum[], series: string[]) { + return data + .filter(isMultiSeriesDatum) + .map((item) => { + const transformed: Record = { + name: item.name, + }; + + series.forEach((serie) => { + transformed[serie] = item.values?.[serie] ?? 0; + }); + + return transformed; + }); +} + +function transformDivergingData(data: EducationChartDatum[]) { + return data + .filter(isDivergingDatum) + .map((item) => ({ + name: item.name, + positive: item.positive, + negative: -Math.abs(item.negative), + })); +} + +function CustomTooltip({ active, payload, label }: TooltipProps) { + if (!active || !payload || payload.length === 0) { + return null; + } + + return ( +
+

{label}

+ {payload.map((entry, index) => ( +

+ {entry.name ?? entry.dataKey}: {String(entry.value)} +

+ ))} +
+ ); +} + +function getChartColor(index: number) { + return DEFAULT_COLORS[index % DEFAULT_COLORS.length]; +} + +function renderPieLabel({ name, percent = 0, x = 0, y = 0, textAnchor = "middle" }: PieLabelProps) { + if (percent < 0.08) { + return null; + } + + return ( + + {`${name ?? ""} ${(percent * 100).toFixed(0)}%`} + + ); +} + +function ChartLegend({ showLegend }: { showLegend: boolean }) { + if (!showLegend) { + return null; + } + + return ; +} + +export default function EducationChartPrimitives({ + chartType, + chartData, + series, + showLegend, + + divergingLabels, +}: { + chartType: EducationChartType; + chartData: EducationChartDatum[]; + series: string[]; + showLegend: boolean; + showTooltip: boolean; + divergingLabels: [string, string]; +}) { + const axisProps = { + tick: { fill: AXIS, fontSize: 12, fontFamily: "serif" }, + axisLine: { stroke: GRID }, + tickLine: { stroke: GRID }, + } as const; + + const gridProps = { + strokeDasharray: "0", + stroke: GRID, + opacity: 1, + } as const; + + const commonMargin = { top: 10, right: 12, left: 6, bottom: 8 }; + + const simpleData = toSimpleData(chartData); + + const chart = (() => { + switch (chartType) { + case "bar": + return ( + + + + + + {simpleData.map((_, index) => ( + + ))} + + + + + + ); + + case "bar-horizontal": + return ( + + + + + + + + + {simpleData.map((_, index) => ( + + ))} + + + + ); + + + + + + + + + + + case "line": + return ( + + + + + + + + + + + ); + + case "area": + return ( + + + + + + + + + + + + + + + + + ); + + + + case "pie": + return ( + + + + + {simpleData.map((_, index) => ( + + ))} + + + + ); + + case "donut": + return ( + + + + + {simpleData.map((_, index) => ( + + ))} + + + + ); + + case "scatter": { + const scatterData = toScatterData(chartData); + const labelMap = new Map(); + const xTicks = Array.from(new Set(scatterData.map((item) => item.x))).sort((a, b) => a - b); + const minTick = xTicks[0] ?? 0; + const maxTick = xTicks[xTicks.length - 1] ?? 1; + + scatterData.forEach((item) => { + labelMap.set(item.x, item.name); + }); + + return ( + + + + labelMap.get(Number(value)) ?? String(value)} + /> + + + + + {scatterData.map((_, index) => ( + + ))} + + + + ); + } + + default: + return ( +
+ Unsupported chart type +
+ ); + } + })(); + + return {chart}; +} diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx new file mode 100644 index 00000000..24d272b7 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx @@ -0,0 +1,180 @@ +import * as z from "zod"; + +import EducationChartPrimitives, { + type EducationChartDatum, + type EducationChartType, +} from "./EducationChartPrimitives"; + +export const slideLayoutId = "education-report-chart-slide"; +export const slideLayoutName = "Education Report Chart Slide"; +export const slideLayoutDescription = + "A split education report slide with one unified schema that supports multiple Recharts chart types in the right panel."; + +const ChartTypeSchema = z.enum([ + "bar", + + "line", + "area", + + "pie", + "donut", + "scatter", +]); + +const SimpleDataSchema = z.object({ + name: z.string().min(1).max(20).meta({ + description: "Simple chart category label.", + }), + value: z.number().min(-100000).max(100000).meta({ + description: "Simple chart numeric value.", + }), +}); + +const MultiSeriesDataSchema = z.object({ + name: z.string().min(1).max(20).meta({ + description: "Grouped/stacked category label.", + }), + values: z.record(z.string(), z.number()).meta({ + description: "Series-to-value map for grouped or stacked charts.", + }), +}); + +const DivergingDataSchema = z.object({ + name: z.string().min(1).max(20).meta({ + description: "Diverging chart category label.", + }), + positive: z.number().min(0).max(100000).meta({ + description: "Positive side value.", + }), + negative: z.number().min(0).max(100000).meta({ + description: "Negative side value.", + }), +}); + +const ScatterDataSchema = z.object({ + x: z.number().min(-100000).max(100000).meta({ + description: "Scatter X coordinate.", + }), + y: z.number().min(-100000).max(100000).meta({ + description: "Scatter Y coordinate.", + }), + name: z.string().min(1).max(20).optional().meta({ + description: "Optional scatter tick label.", + }), +}); + +const UnifiedChartDataSchema = z.union([ + z.array(SimpleDataSchema), + z.array(MultiSeriesDataSchema), + z.array(DivergingDataSchema), + z.array(ScatterDataSchema), +]); + +export const Schema = z.object({ + title: z.string().min(3).max(20).default("Report").meta({ + description: "Left-side report title.", + }), + body: z.string().min(80).max(260).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ).meta({ + description: "Left-side report body paragraph.", + }), + footnote: z.string().min(20).max(150).default( + "(Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.)" + ).meta({ + description: "Left-side footnote line.", + }), + chartTitle: z.string().min(8).max(42).default("Students by Grade Level").meta({ + description: "Right-panel chart heading.", + }), + dateRange: z.string().min(8).max(22).default("Apr 10 - Apr 17").meta({ + description: "Right-panel date range label.", + }), + chartType: ChartTypeSchema.default("area").meta({ + description: + "Chart type selector. Supports bar, grouped, stacked, clustered, diverging, line, area, pie/donut, and scatter.", + }), + chartData: UnifiedChartDataSchema.default([ + { name: "Option A", value: 17.07 }, + { name: "Option B", value: 45.23 }, + { name: "Option C", value: 21.61 }, + { name: "Option D", value: 16.36 }, + ]).meta({ + description: "Unified chart data payload. Shape depends on chartType.", + }), + series: z.array(z.string().min(1).max(20)).max(6).default(["Series A", "Series B"]).meta({ + description: "Series names for grouped/stacked/clustered/area-stacked charts.", + }), + divergingLabels: z.tuple([z.string().min(1).max(24), z.string().min(1).max(24)]).default([ + "Positive", + "Negative", + ]).meta({ + description: "Legend labels for bar-diverging charts.", + }), + showLegend: z.boolean().default(true).meta({ + description: "Show or hide chart legend.", + }), + + showStatusMessage: z.boolean().default(false).meta({ + description: "Show callout message under chart (useful for weekly/performance styles).", + }), + statusMessageTitle: z.string().min(8).max(40).default("You are doing good!").meta({ + description: "Callout headline under chart.", + }), + statusMessageBody: z.string().min(10).max(80).default("You almost reached your goal").meta({ + description: "Callout subtitle under chart.", + }), +}); + +export type SchemaType = z.infer; + + + +const EducationReportChartSlide = ({ data }: { data: Partial }) => { + const slideData = Schema.parse(data); + + const chartHeightClass = slideData.showStatusMessage ? "h-[372px]" : "h-[486px]"; + + return ( +
+
+
+
+

+ {slideData.title} +

+

+ {slideData.body} +

+
+ +

+ {slideData.footnote} +

+
+ +
+

+ {slideData.chartTitle} +

+

+ {slideData.dateRange} +

+ +
+ +
+
+
+
+ ); +}; + +export default EducationReportChartSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx deleted file mode 100644 index 7bff8123..00000000 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportDonutSlide.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import * as z from "zod"; - - -export const slideLayoutId = "education-report-donut-slide"; -export const slideLayoutName = "Education Report Donut Slide"; -export const slideLayoutDescription = - "A report slide with left-side title/content and a right-side donut chart with legend values."; - -const SegmentSchema = z.object({ - label: z.string().min(3).max(12).meta({ - description: "Legend label for one donut chart segment.", - }), - value: z.number().min(1).max(100).meta({ - description: "Percentage value for one chart segment.", - }), - color: z.string().min(4).max(20).meta({ - description: "Hex color value for one chart segment.", - }), -}); - -export const Schema = z.object({ - title: z.string().min(3).max(14).default("Report").meta({ - description: "Main heading in the left content area.", - }), - body: z.string().min(80).max(220).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris." - ).meta({ - description: "Main report paragraph on the left.", - }), - footnote: z.string().min(20).max(110).default( - "(Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt.)" - ).meta({ - description: "Footnote text shown at the bottom of the left area.", - }), - chartTitle: z.string().min(8).max(26).default("Students by Grade Level").meta({ - description: "Heading shown above the donut chart.", - }), - dateRange: z.string().min(8).max(22).default("Apr 10 - Apr 17").meta({ - description: "Date range label under the chart heading.", - }), - segments: z - .array(SegmentSchema) - .min(4) - .max(4) - .default([ - { label: "Option A", value: 17.07, color: "#4A15A8" }, - { label: "Option B", value: 45.23, color: "#5B45AD" }, - { label: "Option C", value: 21.61, color: "#876FC1" }, - { label: "Option D", value: 16.36, color: "#A89ACF" }, - ]) - .meta({ - description: "Four donut segments with labels and percentages.", - }), - -}); - -export type SchemaType = z.infer; - -const EducationReportDonutSlide = ({ data }: { data: Partial }) => { - const { title, body, footnote, chartTitle, dateRange, segments } = data; - - const total = segments?.reduce((sum, item) => sum + item.value, 0) || 0; - let cursor = 0; - const conicStops = segments - ?.map((segment) => { - const start = cursor; - const span = total > 0 ? (segment.value / total) * 100 : 0; - cursor += span; - return `${segment.color} ${start}% ${cursor}%`; - }) - .join(", "); - - return ( -
-
-
-
- - -
- -
-

- {title} -

- -

- {body} -

-
- -

- {footnote} -

-
- -
-

- {chartTitle} -

-

{dateRange}

- -
-
-
-
-
- -
- {segments?.map((segment, index) => ( -
- - {segment.label} - - {segment.value.toFixed(2)}% - -
- ))} -
-
-
- -
- ); -}; - -export default EducationReportDonutSlide; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx index bb0063ec..e980999c 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx @@ -1,14 +1,81 @@ import * as z from "zod"; - +import { + ResponsiveContainer, + BarChart, + Bar, + CartesianGrid, + XAxis, + YAxis, + PieChart, + Pie, + Cell, + LineChart, + Line, + LabelList, +} from "recharts"; export const slideLayoutId = "product-overview-report-snapshot-slide"; export const slideLayoutName = "Product Overview Report Snapshot Slide"; export const slideLayoutDescription = - "A report summary slide with a left-edge photo strip, title and intro copy, a compact bar chart card, and a KPI callout card on the right."; + "A report summary slide with a left-edge photo strip, title and intro copy, a chart card that supports four visual styles, and KPI callout cards on the right."; -const BarSchema = z.object({ +const LegacyBarSchema = z.object({ value: z.number().min(10).max(100).meta({ - description: "Relative bar value used in the spending mini chart.", + description: "Relative bar value used by legacy data.", + }), +}); + +const MiniBarPointSchema = z.object({ + label: z.string().min(1).max(10).meta({ + description: "Category label for the mini bar chart.", + }), + primary: z.number().min(0).max(1000).meta({ + description: "Primary series value.", + }), + secondary: z.number().min(0).max(1000).meta({ + description: "Secondary series value.", + }), +}); + +const DonutPointSchema = z.object({ + name: z.string().min(1).max(20).meta({ + description: "Donut segment label.", + }), + value: z.number().min(1).max(100).meta({ + description: "Donut segment value.", + }), +}); + +const GroupedBarPointSchema = z.object({ + label: z.string().min(1).max(12).meta({ + description: "X-axis label for grouped bars.", + }), + optionA: z.number().min(0).max(220).meta({ + description: "Option A value.", + }), + optionB: z.number().min(0).max(220).meta({ + description: "Option B value.", + }), +}); + +const TrendPointSchema = z.object({ + label: z.string().min(1).max(12).meta({ + description: "X-axis label for trend lines.", + }), + optionA: z.number().min(0).max(100).meta({ + description: "Option A trend value.", + }), + optionB: z.number().min(0).max(100).meta({ + description: "Option B trend value.", + }), +}); + +const MetricCardSchema = z.object({ + value: z.string().min(1).max(8).meta({ + description: "KPI value text.", + }), + body: z.string().min(10).max(40).meta({ + description: "KPI supporting sentence.", }), }); @@ -19,8 +86,8 @@ export const Schema = z.object({ taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ description: "Small label above intro paragraph.", }), - taglineBody: z.string().min(40).max(100).default( - "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." + taglineBody: z.string().min(40).max(120).default( + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ).meta({ description: "Intro paragraph shown beneath the heading.", }), @@ -34,13 +101,90 @@ export const Schema = z.object({ }).meta({ description: "Left-side vertical image strip.", }), + chartStyle: z.enum(["mini-bars", "donut", "grouped-bars", "dual-line"]).default("donut").meta({ + description: "Chart style variant matching Image #1 to Image #4.", + }), chartTitle: z.string().min(3).max(20).default("Sandro Tavares").meta({ description: "Name displayed in the chart card.", }), - bars: z - .array(BarSchema) + miniBars: z + .array(MiniBarPointSchema) .min(8) - .max(8) + .max(9) + .default([ + { label: "1", primary: 320, secondary: 560 }, + { label: "2", primary: 140, secondary: 840 }, + { label: "3", primary: 230, secondary: 520 }, + { label: "4", primary: 320, secondary: 660 }, + { label: "5", primary: 150, secondary: 460 }, + { label: "6", primary: 160, secondary: 850 }, + { label: "7", primary: 640, secondary: 320 }, + { label: "8", primary: 320, secondary: 440 }, + { label: "9", primary: 420, secondary: 620 }, + ]) + .meta({ + description: "Data for Image #1 mini dual-series bar chart.", + }), + donutData: z + .array(DonutPointSchema) + .min(3) + .max(3) + .default([ + { name: "Option A", value: 60 }, + { name: "Option B", value: 20 }, + { name: "Option C", value: 20 }, + ]) + .meta({ + description: "Data for Image #2 donut chart.", + }), + groupedBars: z + .array(GroupedBarPointSchema) + .min(4) + .max(4) + .default([ + { label: "label", optionA: 120, optionB: 200 }, + { label: "label", optionA: 150, optionB: 80 }, + { label: "label", optionA: 70, optionB: 110 }, + { label: "label", optionA: 130, optionB: 130 }, + ]) + .meta({ + description: "Data for Image #3 grouped bar chart.", + }), + trendLines: z + .array(TrendPointSchema) + .min(6) + .max(7) + .default([ + { label: "label", optionA: 8, optionB: 2 }, + { label: "label", optionA: 45, optionB: 65 }, + { label: "label", optionA: 35, optionB: 40 }, + { label: "label", optionA: 95, optionB: 100 }, + { label: "label", optionA: 50, optionB: 35 }, + { label: "label", optionA: 5, optionB: 75 }, + { label: "label", optionA: 55, optionB: 50 }, + ]) + .meta({ + description: "Data for Image #4 dual-line chart.", + }), + legendLabels: z.array(z.string().min(1).max(18)).min(2).max(3).default(["Option A", "Option B", "Option C"]).meta({ + description: "Legend labels used by donut/grouped/line variants.", + }), + xAxisName: z.string().min(3).max(16).default("X axis name").meta({ + description: "X axis title used in the dual-line variant.", + }), + yAxisName: z.string().min(3).max(16).default("Y axis name").meta({ + description: "Y axis title used in the dual-line variant.", + }), + footerLabel: z.string().min(10).max(60).default("Current margin: April Spendings").meta({ + description: "Footer label under mini bar chart.", + }), + footerValue: z.string().min(6).max(24).default("$350.00 / $640.00").meta({ + description: "Footer value under mini bar chart.", + }), + bars: z + .array(LegacyBarSchema) + .min(8) + .max(9) .default([ { value: 52 }, { value: 24 }, @@ -52,14 +196,25 @@ export const Schema = z.object({ { value: 55 }, ]) .meta({ - description: "Eight bars used in the spending card chart.", + description: "Legacy fallback bar values used when miniBars is not supplied.", }), metricValue: z.string().min(1).max(8).default("X 5").meta({ - description: "KPI value in the callout card.", + description: "Legacy single KPI value.", }), - metricBody: z.string().min(10).max(18).default("Lorem ipsum.").meta({ - description: "KPI short text in the callout card.", + metricBody: z.string().min(10).max(40).default("Lorem ipsum dolor sit.").meta({ + description: "Legacy single KPI body.", }), + metricCards: z + .array(MetricCardSchema) + .min(1) + .max(2) + .default([ + { value: "X 5", body: "Lorem ipsum dolor sit." }, + { value: "X 5", body: "Lorem ipsum dolor sit." }, + ]) + .meta({ + description: "One or two KPI cards shown on the right.", + }), metricIcon: z.object({ __icon_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg"), __icon_query__: z.string().min(3).max(30).default("pulse icon"), @@ -74,86 +229,357 @@ export const Schema = z.object({ export type SchemaType = z.infer; +const MINI_BAR_DARK = "#0B4B40"; +const MINI_BAR_LIGHT = "#CED3D1"; +const DONUT_COLORS = ["#0B4B40", "#4B6B61", "#7B938C"]; +const KPI_ICON_BG = "#063C73"; + +const PulseIcon = () => ( + +); + +const renderDonutPercentLabel = ({ cx, cy, midAngle, outerRadius, percent }: any) => { + const radius = (outerRadius ?? 0) + 18; + const x = (cx ?? 0) + radius * Math.cos((-(midAngle ?? 0) * Math.PI) / 180); + const y = (cy ?? 0) + radius * Math.sin((-(midAngle ?? 0) * Math.PI) / 180); + + return ( + + + + {`${Math.round((percent ?? 0) * 100)}%`} + + + ); +}; + const ReportSnapshotSlide = ({ data }: { data: Partial }) => { const { title, taglineLabel, taglineBody, sideImage, + chartStyle, chartTitle, + miniBars, + donutData, + groupedBars, + trendLines, + legendLabels, + xAxisName, + yAxisName, + footerLabel, + footerValue, bars, metricValue, metricBody, + metricCards, metricIcon, } = data; + const resolvedMiniBars = + miniBars && miniBars.length >= 8 + ? miniBars + : (bars ?? []).map((bar, index) => { + const scaledPrimary = Math.round(bar.value * 8 + 80); + const scaledSecondary = Math.min(1000, scaledPrimary + 220); + + return { + label: `${index + 1}`, + primary: scaledPrimary, + secondary: scaledSecondary, + }; + }); + + const fallbackMetric = { + value: metricValue ?? "X 5", + body: metricBody ?? "Lorem ipsum dolor sit.", + }; + + const resolvedMetricCards = + metricCards && metricCards.length > 0 + ? metricCards + : [fallbackMetric, fallbackMetric]; + + const visibleMetricCards = + (chartStyle ?? "mini-bars") === "mini-bars" + ? resolvedMetricCards.slice(0, 1) + : resolvedMetricCards.slice(0, 2); + + const usePulseFallback = !metricIcon?.__icon_url__ || metricIcon.__icon_url__.includes("placeholder.svg"); + const activeChartStyle = chartStyle ?? "mini-bars"; + const donutTotal = (donutData ?? []).reduce((sum, item) => sum + item.value, 0) || 1; + return (
{sideImage?.__image_url__ && ( {sideImage?.__image_prompt__} )} -
-

+
+

{title}

-
-

+

+

{taglineLabel}

-

{taglineBody}

-
-
- -
-

Spendings

-

- {chartTitle} -

- -
- {bars?.map((bar, index) => ( -
- ))} -
- -
-

Current margin: April Spendings

-

$350.00 / $640.00

-
-
- -
-
-
- {metricIcon?.__icon_query__} -
-

- {metricValue} +

+ {taglineBody}

-

- {metricBody} -

+
+ +
+

{chartTitle}

+ + {activeChartStyle === "mini-bars" && ( + <> +
+ + + + + `$${value}`} + axisLine={false} + tickLine={false} + tick={{ fill: "#6C7271", fontSize: 10 }} + /> + + + + +
+ +
+

{footerLabel}

+

{footerValue}

+
+ + )} + + {activeChartStyle === "donut" && ( +
+
+ + + + {(donutData ?? []).map((entry, index) => ( + + ))} + + + +
+ +
+ {(donutData ?? []).map((entry, index) => { + const percent = Math.round((entry.value / donutTotal) * 100); + return ( +
+
+ +

+ {legendLabels?.[index] ?? entry.name} +

+
+

{percent}%

+
+ ); + })} +
+
+ )} + + {activeChartStyle === "grouped-bars" && ( +
+
+ + + + + + + + + + + + + +
+ +
+
+ +

{legendLabels?.[0] ?? "Option A"}

+
+
+ +

{legendLabels?.[1] ?? "Option B"}

+
+
+
+ )} + + {activeChartStyle === "dual-line" && ( +
+
+ + + + + + + + + +
+ +
+
+ +

{legendLabels?.[0] ?? "Option A"}

+
+
+ +

{legendLabels?.[1] ?? "Option B"}

+
+
+
+ )} +
+ +
+
+ {visibleMetricCards.map((metric, index) => ( +
+
+
+ {usePulseFallback ? ( + + ) : ( + {metricIcon?.__icon_query__} + )} +
+

{metric.value}

+
+

{metric.body}

+
+ ))} +
); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx index 6778d07e..f1cc57b2 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx @@ -1,10 +1,13 @@ +"use client"; + import * as z from "zod"; +import { ResponsiveContainer } from "recharts"; -import { WorkflowBarChart } from "./chartPrimitives"; +import { FlexibleReportChart, flexibleChartDataSchema } from "./flexibleReportChart"; const InsightItemSchema = z.object({ - title: z.string().min(3).max(18).meta({ + title: z.string().min(3).max(12).meta({ description: "Short insight title shown next to the icon.", }), description: z.string().min(20).max(84).meta({ @@ -12,15 +15,6 @@ const InsightItemSchema = z.object({ }), }); -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 = @@ -30,16 +24,20 @@ 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.", - }), + 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) @@ -52,22 +50,19 @@ export const Schema = z.object({ .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.", - }), + chartData: flexibleChartDataSchema.default({ + type: "pie", + data: [ + { name: "Mon", value: 120 }, + { name: "Tue", value: 200 }, + { name: "Wed", value: 150 }, + { name: "Thu", value: 80 }, + { name: "Fri", value: 70 }, + { name: "Sat", value: 110 }, + { name: "Sun", value: 130 }, + ], + + }), legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({ description: "Legend label shown below the chart.", }), @@ -76,20 +71,17 @@ export const Schema = z.object({ export type SchemaType = z.infer; const DataAnalysisBarSlide = ({ data }: { data: Partial }) => { - const { title, itemIcon, items, chartData, legendLabel } = data; + const rows = chartData?.data ?? []; + const chartType = chartData?.type ?? "bar"; + const series = chartData?.series ?? []; return (
-
+
-

- {title} -

+

{title}

@@ -105,20 +97,18 @@ const DataAnalysisBarSlide = ({ data }: { data: Partial }) => { style={{ filter: "brightness(0) invert(1)" }} />
-

- {item.title} -

+

{item.title}

-

- {item.description} -

+

{item.description}

))}
- + + +
diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx index c5af4502..02fc5580 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx @@ -1,13 +1,20 @@ +"use client"; + import type { ReactNode } from "react"; import * as z from "zod"; +import { ResponsiveContainer } from "recharts"; + import { - AreaTrendChart, - CompactBarChart, - CompactPieChart, - SemiDonutChart, - TrendLineChart, -} from "./chartPrimitives"; + DivergingDataPointSchema, + FlexibleReportChart, + MultiSeriesDataPointSchema, + ScatterDataPointSchema, + SimpleDataPointSchema, + flexibleChartDataSchema, + flexibleChartTypeSchema, + type FlexibleChartData, +} from "./flexibleReportChart"; const SummaryCardSchema = z.object({ value: z.string().min(1).max(8).meta({ @@ -16,162 +23,111 @@ const SummaryCardSchema = z.object({ 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.", + 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"), + }).optional().meta({ + description: "Icon shown in each compact summary card.", + }).default({ + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "pulse icon", }), }); -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."; + "A dashboard-style slide with a title, summary cards, and a responsive grid of chart panels (1–9). Each panel uses the same flexible chart types as other report slides; labels and margins are compact for small cells."; + +const ChartItemSchema = z.object({ + + type: flexibleChartTypeSchema.default('bar'), + data: z.union([ + z.array(SimpleDataPointSchema), + z.array(MultiSeriesDataPointSchema), + z.array(DivergingDataPointSchema), + z.array(ScatterDataPointSchema), + ]).default([ + { name: 'Q1', value: 45 }, + { name: 'Q2', value: 72 }, + { name: 'Q3', value: 58 }, + { name: 'Q4', value: 89 }, + ]), + series: z.array(z.string()).optional(), +}); export const Schema = z.object({ - title: z.string().min(3).max(28).default("Data Analysis").meta({ + title: z.string().min(3).max(12).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) + .min(2) .max(4) + .optional() .default([ - { value: "5", label: "Text 1" }, - { value: "52", label: "Text 2" }, - { value: "4", label: "Text 3" }, - { value: "80%", label: "Text 4" }, + { + value: "5", label: "Text 1", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "placeholder icon", + } + }, + { + value: "52", label: "Text 2", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "placeholder icon", + } + }, + { + value: "4", label: "Text 3", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "placeholder icon", + } + }, + { + value: "80%", label: "Text 4", icon: { + __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", + __icon_query__: "placeholder icon", + } + }, ]) .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.", - }), + charts: z.array(ChartItemSchema).min(1).max(6).default([ + { type: 'bar', data: [{ name: 'Q1', value: 125000 }, { name: 'Q2', value: 158000 }, { name: 'Q3', value: 142000 }, { name: 'Q4', value: 189000 }] }, + { type: 'donut', data: [{ name: 'North America', value: 35 }, { name: 'Europe', value: 28 }, { name: 'Asia Pacific', value: 25 }, { name: 'Others', value: 12 }] }, + { type: 'line', data: [{ name: 'Jan', value: 30 }, { name: 'Feb', value: 45 }, { name: 'Mar', value: 52 }, { name: 'Apr', value: 48 }, { name: 'May', value: 67 }, { name: 'Jun', value: 82 }] }, + { type: 'bar', data: [{ name: 'Sales', value: 87 }, { name: 'Marketing', value: 72 }, { name: 'Engineering', value: 95 }, { name: 'Support', value: 68 }] }, + { type: 'bar-clustered', data: [{ name: 'Q1', values: { 'Product A': 45, 'Product B': 62 } }, { name: 'Q2', values: { 'Product A': 58, 'Product B': 71 } }, { name: 'Q3', values: { 'Product A': 72, 'Product B': 65 } }], series: ['Product A', 'Product B'] }, + { type: 'bar-diverging', data: [{ name: 'Quality', positive: 78, negative: 22 }, { name: 'Service', positive: 65, negative: 35 }, { name: 'Price', positive: 42, negative: 58 }], series: ['Satisfied', 'Unsatisfied'] }, + ]), }); export type SchemaType = z.infer; + + + + + + + +const getChartHeight = (count: number, hasMetrics: boolean) => { + if (count <= 2) return hasMetrics ? 230 : 280; + if (count <= 3) return hasMetrics ? 210 : 260; + return hasMetrics ? 160 : 180; +}; + + function SummaryCard({ value, label, @@ -184,164 +140,96 @@ function SummaryCard({ iconAlt?: string; }) { return ( -
-
+
+
{iconAlt
-
-

- {value} -

-

{label}

+
+

{value}

+

{label}

); } -function ChartCell({ - children, - footer, - topLegend, -}: { - children: ReactNode; - footer?: ReactNode; - topLegend?: ReactNode; -}) { - return ( -
- {topLegend &&
{topLegend}
} -
{children}
- {footer &&
{footer}
} -
- ); -} -function DotLegend({ - items, -}: { - items: { label: string; color: string }[]; -}) { - return ( -
- {items.map((item) => ( - - - {item.label} - - ))} -
- ); -} + + const DataAnalysisDashboardSlide = ({ data }: { data: Partial }) => { - - const { title, summaryIcon, summaryCards, workflowBars, gaugeSegments, trendSeries, detailedArea, shareBreakdown, comparisonBars } = data; + const { title, summaryCards, charts } = data; + const halfChart = charts?.slice(0, Math.ceil(charts.length / 2)); + const otherHalfChart = charts?.slice(Math.ceil(charts.length / 2)); return ( -
-
+
+
-
-

- {title} -

+
+

{title}

-
+ {summaryCards && summaryCards.length > 0 &&
{summaryCards?.map((card, index) => ( ))} -
+
} +
-
-
- - } + {halfChart && halfChart.length > 0 &&
+
- - + {halfChart?.map((chart, index) => ( +
- } - > - - + > - -

Category A

-

Category B

+
+ +
- } + ))} +
+
} + {otherHalfChart && otherHalfChart.length > 0 &&
+
- - -
+ {otherHalfChart?.map((chart, index) => ( +
- - } - > - - + > - - } - > - - +
+ + + - - } - > - - -
+
+
+ ))} +
+
}
); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx index bb4839a1..ae8d24d7 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx @@ -1,57 +1,54 @@ +"use client"; + import * as z from "zod"; +import { ResponsiveContainer } from "recharts"; -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.", - }), -}); +import { FlexibleReportChart, flexibleChartDataSchema } from "./flexibleReportChart"; 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."; + "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 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({ + title: z.string().min(3).max(12).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 }, - ]) + 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: "Weekly values shown in the right-side bar chart.", + 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: flexibleChartDataSchema.default({ + type: "pie", + data: [ + { name: "Q1", value: 45 }, + { name: "Q2", value: 72 }, + { name: "Q3", value: 58 }, + { name: "Q4", value: 89 }, + ], + }), legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({ description: "Legend label shown below the chart.", }), @@ -64,46 +61,40 @@ const DataAnalysisInsightBarSlide = ({ }: { data: Partial; }) => { - - const { title, insightIcon, insightBody, chartData, legendLabel } = data; + const chartData = data?.chartData?.data ?? []; + const chartType = data?.chartData?.type ?? "bar"; + const series = data?.chartData?.series ?? []; return (
-
+
-

- {title} -

+

{data.title}

-
-
+
+
{insightIcon?.__icon_query__}
-

- {insightBody} -

+

{data.insightBody}

-
-
- -
+
+ + +
-

{legendLabel}

+

{data.legendLabel}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx index c19cb3d7..e946fec1 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx @@ -1,18 +1,11 @@ +"use client"; + +import { Fragment } from "react/jsx-runtime"; import * as z from "zod"; -import { DualLineChart } from "./chartPrimitives"; +import { ResponsiveContainer } from "recharts"; -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.", - }), -}); +import { FlexibleReportChart, flexibleChartDataSchema } from "./flexibleReportChart"; const MetricSchema = z.object({ value: z.string().min(1).max(12).meta({ @@ -38,7 +31,7 @@ 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({ + title: z.string().min(3).max(12).default("Data Analysis").meta({ description: "Slide title shown at the top-left.", }), seriesALabel: z.string().min(3).max(20).default("Category A").meta({ @@ -47,11 +40,9 @@ export const Schema = z.object({ 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([ + chartData: flexibleChartDataSchema.default({ + type: "pie", + data: [ { label: "label", valueA: 24, valueB: 40 }, { label: "label", valueA: 55, valueB: 72 }, { label: "label", valueA: 50, valueB: 98 }, @@ -59,10 +50,11 @@ export const Schema = z.object({ { 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.", - }), + ], + }), + legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({ + description: "Legend label shown below the chart.", + }), statColumns: z .array(StatColumnSchema) .min(2) @@ -94,60 +86,41 @@ type StatMetric = { description: string; }; -function StatPill({ - metrics, - -}: { - metrics: StatMetric[]; - -}) { - - +function StatPill({ metrics }: { metrics: StatMetric[] }) { return ( -
- +
{metrics.map((metric, index) => ( - <> -
-

- {metric.value} -

+ +
+

{metric.value}

{metric.label}

-

- {metric.description} -

+

{metric.description}

- {index === 0 &&
- - - - -
- } - + {index === 0 && ( +
+ + + +
+ )} +
))} - -
); } + const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => { - const { title, seriesALabel, seriesBLabel, lineData, statColumns } = data; + const { title, seriesALabel, seriesBLabel, chartData, statColumns, legendLabel } = data; + const rows = chartData?.data ?? []; + const chartType = chartData?.type ?? "line-dual"; + const series = chartData?.series ?? []; return (
-
+
-

- {title} -

+

{title}

@@ -164,11 +137,20 @@ const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) =>
- + + +
-
- X axis name +
+ +

{data.legendLabel}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx index 60c07657..5f09fad3 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx @@ -2,10 +2,10 @@ import * as z from "zod"; const AnalysisItemSchema = z.object({ - title: z.string().min(3).max(18).meta({ + title: z.string().max(12).meta({ description: "Short item title displayed next to the icon.", }), - description: z.string().min(20).max(84).meta({ + description: z.string().max(30).meta({ description: "Supporting sentence shown below the title.", }), }); @@ -16,7 +16,7 @@ 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({ + title: z.string().min(3).max(12).default("Data Analysis").meta({ description: "Slide title shown at the top-left.", }), itemIcon: z.object({ @@ -31,7 +31,7 @@ export const Schema = z.object({ }), items: z .array(AnalysisItemSchema) - .min(6) + .max(6) .default([ { title: "Title 1", description: "Ut enim ad minima veniam, quis." }, diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx index d84fa166..1af15451 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx @@ -1,10 +1,10 @@ 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"), + title: z.string().min(1).max(12).default("Company's "), + subtitle: z.string().min(1).max(15).default("Report"), + name: z.string().min(1).max(10).default("John Doe"), + position: z.string().min(1).max(20).default("Company Name | Strategy, Content, growth"), }) export type SchemaType = z.infer; export const slideLayoutId = "intro-slide"; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx index b9529372..327e7f8a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx @@ -7,17 +7,17 @@ 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({ + title: z.string().min(3).max(12).default("Introduction").meta({ description: "Slide title shown at the top-left.", }), - body: z.string().min(60).max(280).default( + body: z.string().max(250).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) + .array(z.string().max(35)) + .max(4) .default([ "Ut enim ad minima veniam, quis nostrum", diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx index f938a485..930d58ea 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx @@ -1,13 +1,14 @@ +import { Fragment } from "react/jsx-runtime"; import * as z from "zod"; const MetricSchema = z.object({ - value: z.string().min(1).max(12).meta({ + value: z.string().min(1).max(6).meta({ description: "Primary metric value shown in the card.", }), - label: z.string().min(3).max(24).meta({ + label: z.string().min(3).max(10).meta({ description: "Short metric label shown below the value.", }), - description: z.string().min(6).max(36).meta({ + description: z.string().min(6).max(20).meta({ description: "Supporting text shown below the label.", }), }); @@ -24,17 +25,17 @@ 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({ + title: z.string().min(3).max(12).default("Introduction").meta({ description: "Slide title shown at the top-left.", }), - body: z.string().min(60).max(320).default( + body: z.string().max(250).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) + .array(z.string().max(35)) + .max(4) .default([ "Ut enim ad minima veniam, quis nostrum", @@ -47,7 +48,7 @@ export const Schema = z.object({ }), statColumns: z .array(StatColumnSchema) - .min(2) + .max(2) .default([ { @@ -89,7 +90,7 @@ function StatPill({
{metrics.map((metric, index) => ( - <> +
} - +
))} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx index 5db5a866..94470948 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx @@ -4,10 +4,10 @@ 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({ + heading: z.string().min(3).max(10).meta({ description: "Heading displayed below the milestone marker.", }), - description: z.string().min(20).max(80).meta({ + description: z.string().min(20).max(50).meta({ description: "Supporting milestone description shown under the heading.", }), }); @@ -18,7 +18,7 @@ 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({ + title: z.string().min(3).max(12).default("Milestone").meta({ description: "Slide title shown at the top-left.", }), activeIndex: z.number().int().min(0).max(4).default(4).meta({ diff --git a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx index eea9d05e..fa2902b3 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx @@ -1,13 +1,14 @@ +import { Fragment } from "react/jsx-runtime"; import * as z from "zod"; const MetricSchema = z.object({ - value: z.string().min(1).max(12).meta({ + value: z.string().min(1).max(6).meta({ description: "Primary metric value shown in the pill.", }), - label: z.string().min(3).max(24).meta({ + label: z.string().min(3).max(10).meta({ description: "Short label shown below the metric value.", }), - description: z.string().min(6).max(36).meta({ + description: z.string().min(6).max(20).meta({ description: "Supporting metric description shown below the label.", }), }); @@ -24,7 +25,7 @@ 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({ + title: z.string().min(3).max(12).default("Performance Snapshot").meta({ description: "Slide title shown at the top-left.", }), columns: z @@ -82,7 +83,7 @@ function StatPill({
{metrics.map((metric, index) => ( - <> +
} - +
))} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx index 497eebf1..9df93cdb 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx @@ -16,7 +16,7 @@ const ServiceItemSchema = z.object({ heading: z.string().min(3).max(18).meta({ description: "Heading shown below the service icon.", }), - description: z.string().min(20).max(84).meta({ + description: z.string().min(20).max(50).meta({ description: "Supporting description below the service heading.", }), }); @@ -27,7 +27,7 @@ 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({ + title: z.string().min(3).max(12).default("Services").meta({ description: "Slide title shown at the top-left.", }), diff --git a/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx index e724d4a9..8e881bce 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx @@ -10,13 +10,13 @@ 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: z.string().min(20).max(50).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({ + title: z.string().min(3).max(12).default("Solution").meta({ description: "Slide heading shown in the top-left corner.", }), showImage: z.boolean().default(true).meta({ diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx index 0e4b62f3..21483463 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx @@ -2,10 +2,10 @@ import * as z from "zod"; const MemberSchema = z.object({ - title: z.string().min(2).max(24).meta({ + title: z.string().min(2).max(12).meta({ description: "Short role or title shown above the member name.", }), - name: z.string().min(2).max(32).meta({ + name: z.string().min(2).max(20).meta({ description: "Member name shown at the bottom of the card.", }), image: z.object({ @@ -25,7 +25,7 @@ export const slideLayoutDescription = export const Schema = z.object({ members: z .array(MemberSchema) - .min(5) + .min(2) .max(5) .default([ { diff --git a/electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx b/electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx deleted file mode 100644 index 87b7b82f..00000000 --- a/electron/servers/nextjs/app/presentation-templates/Report/chartPrimitives.tsx +++ /dev/null @@ -1,338 +0,0 @@ -"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 ( - cx ? "start" : "end"} - dominantBaseline="central" - fontSize="10" - fontWeight="500" - > - {(percent * 100).toFixed(1)}% - - ); -} - -export function CompactBarChart({ - data, -}: { - data: SimpleBarDatum[]; -}) { - return ( - - - - - - - - - - - ); -} - -export function WorkflowBarChart({ - data, -}: { - data: SimpleBarDatum[]; -}) { - return ( - - - - - - - - - - - ); -} - -export function TrendLineChart({ - data, -}: { - data: DualSeriesDatum[]; -}) { - return ( - - - - - - - - - - ); -} - -export function DualLineChart({ - data, -}: { - data: DualSeriesDatum[]; -}) { - return ( - - - - - - - - - - ); -} - -export function AreaTrendChart({ - data, - idPrefix, -}: { - data: SimpleBarDatum[]; - idPrefix: string; -}) { - return ( - - - - - - - - - - - - - - - ); -} - -export function SemiDonutChart({ - data, -}: { - data: PieDatum[]; -}) { - return ( - - - - {data.map((entry, index) => ( - - ))} - - - - ); -} - -export function CompactPieChart({ - data, -}: { - data: PieDatum[]; -}) { - return ( - - - - {data.map((entry, index) => ( - - ))} - - - - ); -} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx b/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx new file mode 100644 index 00000000..75be9b1c --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx @@ -0,0 +1,834 @@ +"use client"; + +import { useId } from "react"; +import * as z from "zod"; +import { + Area, + AreaChart, + Bar, + BarChart, + CartesianGrid, + Cell, + Line, + LineChart, + Pie, + PieChart, + ReferenceLine, + Scatter, + ScatterChart, + XAxis, + YAxis, + LabelList, + ResponsiveContainer, +} from "recharts"; + +export const simpleDataSchema = z.object({ + name: z.string().meta({ description: "Data point name" }), + value: z.number().meta({ description: "Data point value" }), +}); + +export const multiSeriesDataSchema = z.object({ + name: z.string().meta({ description: "Category name" }), + values: z.any().meta({ + description: + "Key-value pairs for each series (object with series names as keys and numbers as values)", + }), +}); + +export const divergingDataSchema = z.object({ + name: z.string().meta({ description: "Category name" }), + positive: z.number().meta({ description: "Positive value" }), + negative: z.number().meta({ description: "Negative value" }), +}); + +export const scatterDataSchema = z.object({ + x: z.number().meta({ description: "X coordinate" }), + y: z.number().meta({ description: "Y coordinate" }), +}); + +/** Two series over categorical labels (line stats slide). */ +export const dualLinePointSchema = z.object({ + label: z.string().meta({ description: "Chart axis label" }), + valueA: z.number().meta({ description: "First series value" }), + valueB: z.number().meta({ description: "Second series value" }), +}); +export const SimpleDataPointSchema = z.object({ + name: z.string(), + value: z.number(), +}); + +export const MultiSeriesDataPointSchema = z.object({ + name: z.string(), + values: z.any(), +}); + +export const DivergingDataPointSchema = z.object({ + name: z.string(), + positive: z.number(), + negative: z.number(), +}); + +export const ScatterDataPointSchema = z.object({ + x: z.number(), + y: z.number(), + name: z.string().optional(), +}); + + +export const flexibleChartTypeSchema = z.enum([ + "bar", + "bar-horizontal", + "bar-grouped-vertical", + "bar-grouped-horizontal", + "bar-stacked-vertical", + "bar-stacked-horizontal", + "bar-clustered", + "bar-diverging", + "line", + "line-dual", + "area", + "area-stacked", + "pie", + "donut", + "scatter", +]); + +export const flexibleChartDataSchema = z.object({ + type: flexibleChartTypeSchema.default("bar"), + data: z.union([ + z.array(simpleDataSchema), + z.array(multiSeriesDataSchema), + z.array(divergingDataSchema), + z.array(scatterDataSchema), + z.array(dualLinePointSchema), + ]), + series: z.array(z.string()).optional().meta({ description: "Series names for grouped/stacked charts" }), + divergingLabels: z.tuple([z.string(), z.string()]).optional(), +}); + +export type FlexibleChartData = z.infer; + +const formatComma = (value: number) => value.toLocaleString("en-US"); + +export function deriveSeriesNames(data: any[], explicit: string[]): string[] { + if (explicit.length > 0) return explicit; + const first = data[0]; + if (!first) return []; + if (first.values != null && typeof first.values === "object" && !Array.isArray(first.values)) { + return Object.keys(first.values); + } + if (typeof first.value === "number") return ["value"]; + return []; +} + +export function transformMultiSeriesData(data: any[], series: string[]) { + return data.map((item) => { + const result: Record = { name: item.name }; + series.forEach((s) => { + if (item.values != null && typeof item.values === "object" && s in item.values) { + result[s] = Number(item.values[s]) || 0; + } else if (s === "value" && typeof item.value === "number") { + result[s] = item.value; + } else if (typeof item[s] === "number") { + result[s] = item[s]; + } else { + result[s] = Number(item.values?.[s]) || 0; + } + }); + return result; + }); +} + +export function transformDivergingData(data: any[]) { + return data.map((item) => { + if (typeof item.positive === "number" && typeof item.negative === "number") { + return { + name: item.name, + positive: item.positive, + negative: -Math.abs(item.negative), + }; + } + const v = Number(item.value); + if (!Number.isNaN(v)) { + return { + name: item.name, + positive: Math.max(0, v), + negative: v < 0 ? v : 0, + }; + } + return { name: item.name, positive: 0, negative: 0 }; + }); +} + +export function normalizeScatterPoints(data: any[]) { + return data.map((item, i) => { + if (typeof item.x === "number" && typeof item.y === "number") { + return { ...item, x: item.x, y: item.y }; + } + if (typeof item.value === "number") { + return { ...item, x: typeof item.x === "number" ? item.x : i + 1, y: item.value }; + } + return { ...item, x: i + 1, y: 0 }; + }); +} + +/** Line-stats style rows: categorical `label` + two metrics (not a single `value` series). */ +function dataIsDualLineShape(data: any[]): boolean { + const row = data[0]; + return ( + !!row && + typeof row === "object" && + typeof row.label === "string" && + typeof row.valueA === "number" && + typeof row.valueB === "number" && + typeof row.value !== "number" + ); +} + +const MULTI_SERIES_CHART_TYPES: FlexibleChartData["type"][] = [ + "bar-grouped-vertical", + "bar-grouped-horizontal", + "bar-stacked-vertical", + "bar-stacked-horizontal", + "bar-clustered", + "area-stacked", +]; + +/** + * Aligns `data`/`series` with `chartType`. Line-stats slides often keep `{ label, valueA, valueB }` + * while bar/line/pie/etc. expect `name`/`value` or `values` + series keys. + */ +export function normalizeFlexibleChartData( + chartType: FlexibleChartData["type"], + data: any[], + seriesIn: string[], +): { data: any[]; series: string[] } { + const series = seriesIn ?? []; + const rows = data ?? []; + + if (chartType === "line-dual") { + if (dataIsDualLineShape(rows)) return { data: rows, series }; + return { + data: rows.map((r, i) => ({ + label: r.label ?? r.name ?? `P${i + 1}`, + valueA: typeof r.valueA === "number" ? r.valueA : typeof r.value === "number" ? r.value : 0, + valueB: typeof r.valueB === "number" ? r.valueB : typeof r.value === "number" ? r.value : 0, + })), + series, + }; + } + + if (!dataIsDualLineShape(rows)) { + return { data: rows, series }; + } + + const dual = rows as Array<{ label: string; valueA: number; valueB: number }>; + + if (MULTI_SERIES_CHART_TYPES.includes(chartType)) { + const keys = series.length >= 2 ? [series[0], series[1]] : ["A", "B"]; + const mapped = dual.map((r) => ({ + name: r.label, + values: { [keys[0]]: r.valueA, [keys[1]]: r.valueB }, + })); + return { data: mapped, series: keys }; + } + + if (chartType === "bar-diverging") { + const mapped = dual.map((r) => ({ + name: r.label, + positive: Math.max(0, r.valueA), + negative: Math.max(0, r.valueB), + })); + return { data: mapped, series }; + } + + const mapped = dual.map((r) => ({ + name: r.label, + value: r.valueA + r.valueB, + })); + return { data: mapped, series }; +} + +const graphVar = (index: number, fallback: string) => `var(--graph-${index % 8}, ${fallback})`; + +export type ChartDensity = "default" | "compact"; + +export type FlexibleReportChartProps = { + chartType: FlexibleChartData["type"]; + data: any[]; + series?: string[]; + colorFallback?: string; + /** For `line-dual` only */ + dualLineColors?: [string, string]; + /** Smaller type, margins, and labels for multi-chart dashboards */ + density?: ChartDensity; +}; + +export function FlexibleReportChart({ + chartType, + data: chartData, + series = [], + colorFallback = "#157CFF", + dualLineColors = ["#9fb6ff", "#4d4ef3"], + density = "default", +}: FlexibleReportChartProps) { + const areaGradientId = `flex-area-${useId().replace(/:/g, "")}`; + const compact = density === "compact"; + + const { data: normalizedData, series: normalizedSeries } = normalizeFlexibleChartData( + chartType, + chartData, + series, + ); + const effectiveSeries = deriveSeriesNames(normalizedData as any[], normalizedSeries); + const scatterPoints = normalizeScatterPoints(normalizedData as any[]); + + const ui = { + tickFs: compact ? 6 : 10, + catAxisW: compact ? 36 : 60, + barSize: compact ? 12 : 35, + labelFs: compact ? 8 : 14, + labelOffTop: compact ? 2 : 10, + labelOffSide: compact ? 2 : 8, + margin: compact + ? { top: 10, right: 15, left: -2, bottom: 0 } + : { top: 15, right: 20, left: 0, bottom: 5 }, + lineStroke: compact ? 2 : 3, + dotR: compact ? 2.5 : 4, + dotStroke: compact ? 1 : 2, + barRadiusLg: compact ? ([4, 4, 0, 0] as const) : ([8, 8, 0, 0] as const), + barRadiusMd: compact ? ([2, 2, 0, 0] as const) : ([4, 4, 0, 0] as const), + barRadiusH: compact ? ([0, 4, 4, 0] as const) : ([0, 6, 6, 0] as const), + pieOuter: compact ? "100%" : "100%", + donutOuter: compact ? "90%" : "100%", + donutInner: compact ? "60%" : "70%", + pieMargin: compact + ? { top: 0, right: 0, left: 0, bottom: 0 } + : { top: 8, right: 8, left: 8, bottom: 8 }, + pieLabelMinPct: compact ? 0.12 : 0.06, + pieMaxNameLen: compact ? 6 : 10, + }; + + const axisProps = { + tick: { fill: "var(--background-text, #232223)", fontSize: ui.tickFs, fontWeight: 500 }, + axisLine: { stroke: "var(--background-text, #232223)" }, + tickLine: { stroke: "var(--background-text, #232223)" }, + }; + + const gridProps = { + strokeDasharray: "3 3", + stroke: "var(--background-text, #232223)", + opacity: 0.7, + }; + + const renderPieInsideLabel = (props: any) => { + const { + cx = 0, + cy = 0, + midAngle = 0, + innerRadius: ir = 0, + outerRadius: or = 0, + percent = 0, + name, + } = props; + if (percent < ui.pieLabelMinPct) return null; + const toNum = (v: unknown) => { + if (typeof v === "number" && Number.isFinite(v)) return v; + if (typeof v === "string" && v.trim().endsWith("%")) return NaN; + const n = Number(v); + return Number.isFinite(n) ? n : NaN; + }; + let inner = toNum(ir); + let outer = toNum(or); + if (!Number.isFinite(outer)) { + outer = compact ? 56 : 140; + inner = Number.isFinite(inner) ? inner : 0; + } + if (!Number.isFinite(inner)) inner = 0; + const midR = inner + (outer - inner) * 0.5; + const rad = (-midAngle * Math.PI) / 180; + const x = cx + midR * Math.cos(rad); + const y = cy + midR * Math.sin(rad); + const nm = String(name ?? ""); + const short = nm.length <= ui.pieMaxNameLen; + const pct = `${(percent * 100).toFixed(0)}%`; + const fontSize = compact ? (short ? 6 : 5) : short ? 10 : 9; + const labelText = compact ? pct : short ? `${name} ${pct}` : pct; + return ( + + {labelText} + + ); + }; + + const commonProps = { + margin: ui.margin, + + }; + + switch (chartType) { + case "bar": + return ( + + + + + + + + + + + + + ); + + case "bar-horizontal": + return ( + + + + + + + + + + + + ); + + case "bar-grouped-vertical": { + const transformedData = transformMultiSeriesData(normalizedData as any[], effectiveSeries); + return ( + + + + + + + {effectiveSeries.map((s: string, index: number) => ( + + + + ))} + + + ); + } + + case "bar-grouped-horizontal": { + const transformedData = transformMultiSeriesData(normalizedData as any[], effectiveSeries); + return ( + + + + + + + {effectiveSeries.map((s: string, index: number) => ( + + + + ))} + + + ); + } + + case "bar-stacked-vertical": { + const transformedData = transformMultiSeriesData(normalizedData as any[], effectiveSeries); + return ( + + + + + + + {effectiveSeries.map((s: string, index: number) => ( + + + + ))} + + + ); + } + + case "bar-stacked-horizontal": { + const transformedData = transformMultiSeriesData(normalizedData as any[], effectiveSeries); + return ( + + + + + + + {effectiveSeries.map((s: string, index: number) => ( + + + + ))} + + + ); + } + + case "bar-clustered": { + const transformedData = transformMultiSeriesData(normalizedData as any[], effectiveSeries); + return ( + + + + + + + {effectiveSeries.map((s: string, index: number) => ( + + + + ))} + + + ); + } + + case "bar-diverging": { + const transformedData = transformDivergingData(normalizedData as any[]); + return ( + + + + + + + + + + + + + + + ); + } + + case "line": + return ( + + + + + + + + + + ); + + case "line-dual": + return ( + + + + + + + + + + ); + + case "area": + return ( + + + + + + + + + + + + + + + + + ); + + case "area-stacked": { + const transformedData = transformMultiSeriesData(normalizedData as any[], effectiveSeries); + return ( + + + + + + + {effectiveSeries.map((s: string, index: number) => ( + + ))} + + + ); + } + + case "pie": + return ( + + + + + {(normalizedData as any[]).map((_, index) => ( + + ))} + + + + ); + + case "donut": + return ( + + + + + {(normalizedData as any[]).map((_, index) => ( + + ))} + + + + ); + + case "scatter": + return ( + + + + + + + + {scatterPoints.map((_, index) => ( + + ))} + + + + ); + + default: + return ( +
Unsupported chart type
+ ); + } +} diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index e35e7e0c..d2783b16 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -22,7 +22,7 @@ import EducationTableOfContentsSlide, { Schema as EduTocSchema, slideLayoutId as import EducationAboutSlide, { Schema as EduAboutSchema, slideLayoutId as EduAboutId, slideLayoutName as EduAboutName, slideLayoutDescription as EduAboutDesc } from "./Education/EducationAboutSlide"; import EducationContentSplitSlide, { Schema as EduContentSplitSchema, slideLayoutId as EduContentSplitId, slideLayoutName as EduContentSplitName, slideLayoutDescription as EduContentSplitDesc } from "./Education/EducationContentSplitSlide"; import EducationImageGallerySlide, { Schema as EduImageGallerySchema, slideLayoutId as EduImageGalleryId, slideLayoutName as EduImageGalleryName, slideLayoutDescription as EduImageGalleryDesc } from "./Education/EducationImageGallerySlide"; -import EducationReportDonutSlide, { Schema as EduReportDonutSchema, slideLayoutId as EduReportDonutId, slideLayoutName as EduReportDonutName, slideLayoutDescription as EduReportDonutDesc } from "./Education/EducationReportDonutSlide"; +import EducationReportDonutSlide, { Schema as EduReportDonutSchema, slideLayoutId as EduReportDonutId, slideLayoutName as EduReportDonutName, slideLayoutDescription as EduReportDonutDesc } from "./Education/EducationReportChartSlide"; import EducationServicesSplitSlide, { Schema as EduServicesSplitSchema, slideLayoutId as EduServicesSplitId, slideLayoutName as EduServicesSplitName, slideLayoutDescription as EduServicesSplitDesc } from "./Education/EducationServicesSplitSlide"; import EducationStatisticsGridSlide, { Schema as EduStatisticsGridSchema, slideLayoutId as EduStatisticsGridId, slideLayoutName as EduStatisticsGridName, slideLayoutDescription as EduStatisticsGridDesc } from "./Education/EducationStatisticsGridSlide"; import EducationTimelineSlide, { Schema as EduTimelineSchema, slideLayoutId as EduTimelineId, slideLayoutName as EduTimelineName, slideLayoutDescription as EduTimelineDesc } from "./Education/EducationTimelineSlide"; From 950ca497b18fe7ba488864397c9eeb05dca90f67 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Thu, 9 Apr 2026 01:44:18 +0545 Subject: [PATCH 06/18] feat: Theme implementation in new templates --- .../Components/MarketOpportunitySlide.tsx | 133 ++++++++++++ .../Code/CodeSlide01RoadmapCover.tsx | 23 +- .../Code/CodeSlide02CodeExplanationSplit.tsx | 44 +++- .../Code/CodeSlide03ApiRequestResponse.tsx | 87 ++++++-- .../Code/CodeSlide04FeatureGrid.tsx | 36 +++- .../Code/CodeSlide05ComparisonTable.tsx | 62 ++++-- .../Code/CodeSlide06Workflow.tsx | 39 +++- .../Code/CodeSlide07UseCaseList.tsx | 35 ++- .../Code/CodeSlide08CodeExplanationText.tsx | 23 +- .../Code/CodeSlide09TableOfContent.tsx | 29 ++- .../Code/CodeSlide10MetricsSplit.tsx | 42 ++-- .../Code/CodeSlide11MetricsGrid.tsx | 33 ++- .../Education/EducationAboutSlide.tsx | 36 +++- .../Education/EducationChartPrimitives.tsx | 36 ++-- .../Education/EducationContentSplitSlide.tsx | 16 +- .../Education/EducationCoverSlide.tsx | 23 +- .../Education/EducationImageGallerySlide.tsx | 12 +- .../Education/EducationReportChartSlide.tsx | 30 +-- .../Education/EducationServicesSplitSlide.tsx | 23 +- .../EducationStatisticsGridSlide.tsx | 38 ++-- .../EducationTableOfContentsSlide.tsx | 18 +- .../Education/EducationTimelineSlide.tsx | 42 ++-- .../BusinessChallengesCardsSlide.tsx | 40 +++- .../BusinessChallengesGridSlide.tsx | 25 ++- .../ProductOverview/ComparisonChartSlide.tsx | 57 +++-- .../ComparisonTableWithTextSlide.tsx | 72 +++++-- .../ProductOverview/CoverSlide.tsx | 17 +- .../ProductOverview/ImageGallerySlide.tsx | 28 ++- .../ProductOverview/IntroductionSlide.tsx | 17 +- .../ProductOverview/KpiCardsSlide.tsx | 40 +++- .../MarketOpportunitySlide.tsx | 54 +++-- .../ProductOverview/MeetTeamSlide.tsx | 38 +++- .../ProductOverview/MissionVisionSlide.tsx | 45 +++- .../ProductOverview/OurServicesSlide.tsx | 46 +++- .../ProductOverview/PricingPlanSlide.tsx | 38 +++- .../ProductOverview/ProcessSlide.tsx | 33 ++- .../ProductOverview/ReportSnapshotSlide.tsx | 204 ++++++++++++++---- .../ProductOverview/TableOfContentSlide.tsx | 35 ++- .../Report/DataAnalysisBarSlide.tsx | 52 ++++- .../Report/DataAnalysisDashboardSlide.tsx | 58 ++++- .../Report/DataAnalysisInsightBarSlide.tsx | 45 +++- .../Report/DataAnalysisLineStatsSlide.tsx | 62 ++++-- .../Report/DataAnalysisListSlide.tsx | 33 ++- .../Report/IntroSlide.tsx | 39 +++- .../Report/IntroductionImageSlide.tsx | 27 ++- .../Report/IntroductionStatsSlide.tsx | 40 +++- .../Report/MilestoneSlide.tsx | 35 ++- .../Report/PerformanceSnapshotSlide.tsx | 33 ++- .../Report/ServicesSlide.tsx | 31 ++- .../Report/SolutionSlide.tsx | 28 ++- .../Report/TeamSlide.tsx | 24 ++- .../Report/flexibleReportChart.tsx | 49 +++-- .../app/presentation-templates/index.tsx | 4 +- 53 files changed, 1710 insertions(+), 499 deletions(-) create mode 100644 electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/MarketOpportunitySlide.tsx diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/MarketOpportunitySlide.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/MarketOpportunitySlide.tsx new file mode 100644 index 00000000..55028a94 --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/MarketOpportunitySlide.tsx @@ -0,0 +1,133 @@ +import * as z from "zod"; + +export const slideLayoutId = "product-overview-market-opportunity-slide"; +export const slideLayoutName = "Product Overview Market Opportunity Slide"; +export const slideLayoutDescription = + "A market opportunity slide with title and intro text on the left, four bullet lines extending toward the right, and concentric value circles as the visual focal point."; + +const BulletSchema = z.object({ + text: z.string().min(12).max(46).meta({ + description: "Bullet text shown on the left side of a line.", + }), +}); + +export const Schema = z.object({ + title: z.string().min(8).max(22).default("Market Opportunity").meta({ + description: "Main heading shown at the top-left.", + }), + subtitle: z.string().min(40).max(110).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt." + ).meta({ + description: "Supporting text under the main heading.", + }), + bullets: z + .array(BulletSchema) + .min(4) + .max(4) + .default([ + { text: "Ut enim ad minim veniam, quis" }, + { text: "Ut enim ad minim veniam, quis" }, + { text: "Ut enim ad minim veniam, quis" }, + { text: "Ut enim ad minim veniam, quis" }, + ]) + .meta({ + description: "Four bullet-line entries shown on the left.", + }), + values: z + .array(z.string().min(2).max(6)) + .min(4) + .max(4) + .default(["$33", "$20", "$120", "$200"]) + .meta({ + description: "Four values shown from outer to inner circles.", + }), +}); + +export type SchemaType = z.infer; + + +const COLORS = [ + "var(--graph-0,#5f7f79)", + "var(--graph-1,#1f5a4f)", + "var(--graph-2,#0d4f43)", + "var(--graph-3,#06463d)", +]; + +const MarketOpportunitySlide = ({ data }: { data: Partial }) => { + const { title, subtitle, bullets, values } = data; + + return ( +
+
+

+ {title} +

+

+ {subtitle} +

+
+ +
+ {bullets?.map((bullet, index) => ( +
+ +

+ {bullet.text} +

+ + +
+ ))} +
+ +
+ {values?.map((value, index) => ( +
+

+ {value} +

+
+ ))} +
+
+ ); +}; + +export default MarketOpportunitySlide; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx index 4eefd0ee..7c0ff024 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx @@ -32,16 +32,29 @@ export type SchemaType = z.infer; const CodeSlide01RoadmapCover = ({ data }: { data: Partial }) => { return ( -
+
-

{data.companyName}

-

+

{data.companyName}

+

{data.title}

-

{data.subtitle}

+

{data.subtitle}

-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx index 7edb0a22..eda51568 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx @@ -64,15 +64,36 @@ const CodeSlide02CodeExplanationSplit = ({ }) => { return ( -
+
-

{data.title}

+

{data.title}

-
-

{data.codeSnippet?.fileName}

-
+          
+

+ {data.codeSnippet?.fileName} +

+
 
               
                 {data.codeSnippet?.content}
@@ -81,15 +102,22 @@ const CodeSlide02CodeExplanationSplit = ({
           
-

{data.explanationTitle}

-

+

{data.explanationTitle}

+

{data.explanation}

-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx index 9fe00314..3c8c74d6 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx @@ -73,31 +73,64 @@ const CodeSlide03ApiRequestResponse = ({ }) => { return ( -
+
-

{data.title}

+

{data.title}

-
-
-

+

+
+

{data.method}

-

{data.endpoint}

+

{data.endpoint}

-

Headers

-
+

Headers

+
{data.headers?.map((item) => ( -

{item}

+

{item}

))}
-
-

{data.requestSnippet?.fileName}

-
+            
+

+ {data.requestSnippet?.fileName} +

+
 
                 
                   {data.requestSnippet?.content}
@@ -106,9 +139,24 @@ const CodeSlide03ApiRequestResponse = ({
             
-
-

{data.responseSnippet?.fileName}

-
+          
+

+ {data.responseSnippet?.fileName} +

+
 
               
                 {data.responseSnippet?.content}
@@ -118,7 +166,14 @@ const CodeSlide03ApiRequestResponse = ({
         
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx index 950b2dc6..5062cd8c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx @@ -99,24 +99,37 @@ const CodeSlide04FeatureGrid = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

{data?.features?.map((feature) => (
-

{feature.title}

- +

{feature.title}

+ {feature.icon.__icon_query__} }) => { />
-

{feature.description}

+

{feature.description}

))}
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx index 2ff35020..0dbd5388 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx @@ -51,28 +51,51 @@ export type SchemaType = z.infer; function renderCell(value: string) { if (value.toLowerCase() === "check") { - return ; + return ; } - return {value}; + return {value}; } const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

-
-
+
{data?.tableColumns?.map((column) => ( -

{column}

+

+ {column} +

))}
@@ -85,17 +108,32 @@ const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) => gridTemplateColumns: `repeat(${data?.tableColumns?.length || 1}, 1fr)`, }} > -

{row.feature}

-
{renderCell(row.column1)}
-
{renderCell(row.column2)}
-
{renderCell(row.column3)}
+

+ {row.feature} +

+
{renderCell(row.column1)}
+
{renderCell(row.column2)}
+
{renderCell(row.column3)}
))}
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx index f573e69e..2b90ee1a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx @@ -81,33 +81,47 @@ export type SchemaType = z.infer; const CodeSlide06Workflow = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

{data?.steps?.map((step, index) => (
-
+
{step.icon.__icon_query__}
-

{step.title}

-

{step.description}

+

{step.title}

+

{step.description}

{index < (data?.steps?.length || 0) - 1 && ( - - + + )} @@ -115,7 +129,14 @@ const CodeSlide06Workflow = ({ data }: { data: Partial }) => {
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx index 2d44f2a5..8d0f764e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx @@ -36,31 +36,52 @@ export type SchemaType = z.infer; const CodeSlide07UseCaseList = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

{data?.items?.map((item, index) => (
- + {index + 1} -

{item}

+

{item}

))}
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx index b22c127f..871d2155 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx @@ -32,16 +32,29 @@ export type SchemaType = z.infer; const CodeSlide08CodeExplanationText = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

-

{data.explanationTitle}

-

{data.explanation}

+

{data.explanationTitle}

+

{data.explanation}

-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx index aa72323d..a1aebc91 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx @@ -49,15 +49,15 @@ function TocColumn({ items }: { items: { number: string; label: string, descript return ( -
+
- +
-

{item.number}

-

{item.label}

- {item.description &&

{item.description}

} +

{item.number}

+

{item.label}

+ {item.description &&

{item.description}

}
); @@ -72,11 +72,17 @@ const CodeSlide09TableOfContent = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

@@ -84,7 +90,14 @@ const CodeSlide09TableOfContent = ({ data }: { data: Partial }) => {
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx index 666ba65c..7cfeda87 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx @@ -52,35 +52,53 @@ export const Schema = z.object({ export type SchemaType = z.infer; const CodeSlide10MetricsSplit = ({ data }: { data: Partial }) => { - const slideData = Schema.parse(data); + return ( -
+
-

{slideData.title}

+

{data.title}

-

{slideData.explanationTitle}

-

{slideData.explanation}

+

{data.explanationTitle}

+

{data.explanation}

- {slideData.metrics.map((metric, index) => ( + {data?.metrics?.map((metric, index) => (
-

{metric.value}

-

{metric.label}

-

{metric.period}

+

{metric.value}

+

{metric.label}

+

{metric.period}

))}
-
- {slideData.pageLabel} +
+ {data?.pageLabel}
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx index ac0d5659..5e9761cc 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx @@ -46,27 +46,46 @@ export type SchemaType = z.infer; const CodeSlide11MetricsGrid = ({ data }: { data: Partial }) => { return ( -
+
-

{data.title}

+

{data.title}

{data?.metrics?.map((metric, index) => (
-

{metric.value}

-

{metric.label}

-

{metric.period}

+

{metric.value}

+

{metric.label}

+

{metric.period}

))}
-
+
{data.pageLabel}
diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx index b8502868..4bb11c5d 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx @@ -56,16 +56,22 @@ const EducationAboutSlide = ({ data }: { data: Partial }) => { return (<> -
+
-

+

{data.companyName}

-

+

{data.intro}

-

+

{data.body}

@@ -77,8 +83,15 @@ const EducationAboutSlide = ({ data }: { data: Partial }) => { alt={data.topFeatureImage?.__image_prompt__} className="absolute inset-0 h-full w-full object-cover z-1 " /> -
-

+

+
+

{data.topPanelText}

@@ -96,8 +109,15 @@ const EducationAboutSlide = ({ data }: { data: Partial }) => {
-
-

+

+
+

{data.bottomPanelText}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx index a27e2a44..151077c6 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx @@ -84,17 +84,17 @@ type PieLabelProps = { }; const DEFAULT_COLORS = [ - "#4A15A8", - "#5B45AD", - "#7E6CC0", - "#9F94CD", - "#6A31B8", - "#4D2A97", + "var(--graph-0,#4A15A8)", + "var(--graph-1,#5B45AD)", + "var(--graph-2,#7E6CC0)", + "var(--graph-3,#9F94CD)", + "var(--graph-4,#6A31B8)", + "var(--graph-5,#4D2A97)", ]; -const AXIS = "#7C7A83"; -const GRID = "#CFCBD8"; -const PRIMARY_TEXT = "#3E3C45"; +const AXIS = "var(--background-text,#7C7A83)"; +const GRID = "var(--stroke,#CFCBD8)"; +const PRIMARY_TEXT = "var(--background-text,#3E3C45)"; function formatComma(value: number | string) { if (typeof value === "number") { @@ -201,7 +201,7 @@ const renderPieInsideLabel = (props: any) => { y={y} textAnchor="middle" dominantBaseline="central" - fill="#ffffff" + fill="var(--primary-text,#ffffff)" fontSize={fontSize} fontWeight={600} style={{ @@ -247,10 +247,16 @@ function CustomTooltip({ active, payload, label }: TooltipProps) { } return ( -
-

{label}

+
+

{label}

{payload.map((entry, index) => ( -

+

{entry.name ?? entry.dataKey}: {String(entry.value)}

))} @@ -268,7 +274,7 @@ function renderPieLabel({ name, percent = 0, x = 0, y = 0, textAnchor = "middle" } return ( - + {`${name ?? ""} ${(percent * 100).toFixed(0)}%`} ); @@ -298,7 +304,7 @@ export default function EducationChartPrimitives({ divergingLabels: [string, string]; }) { const axisProps = { - tick: { fill: AXIS, fontSize: 12, fontFamily: "serif" }, + tick: { fill: AXIS, fontSize: 12, fontFamily: "var(--body-font-family,'Times New Roman')" }, axisLine: { stroke: GRID }, tickLine: { stroke: GRID }, } as const; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx index 0061b169..500039c5 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx @@ -36,7 +36,13 @@ const EducationContentSplitSlide = ({ data }: { data: Partial }) => const { heading, tagline, body, collageImage } = data; return ( -
+
@@ -61,11 +67,11 @@ const EducationContentSplitSlide = ({ data }: { data: Partial }) =>
-

{data.heading}

-

- {data.tagline} +

{heading}

+

+ {tagline}

-

{body}

+

{body}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx index 5e1ced4e..d081716c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx @@ -31,18 +31,33 @@ const EducationCoverSlide = ({ data }: { data: Partial }) => { const { companyName, title, backgroundImage } = data; return ( -
+
{backgroundImage?.__image_prompt__} -
+
-
-

{data.companyName}

+
+

{companyName}

{title}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx index 0f12403b..1d520d10 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx @@ -33,7 +33,13 @@ const EducationImageGallerySlide = ({ data }: { data: Partial }) => const { title, body, galleryImages } = data; return ( -
+
}) =>
-

+

{title}

-

+

{body}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx index 24d272b7..3a75cfae 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx @@ -90,7 +90,7 @@ export const Schema = z.object({ dateRange: z.string().min(8).max(22).default("Apr 10 - Apr 17").meta({ description: "Right-panel date range label.", }), - chartType: ChartTypeSchema.default("area").meta({ + chartType: ChartTypeSchema.default("bar").meta({ description: "Chart type selector. Supports bar, grouped, stacked, clustered, diverging, line, area, pie/donut, and scatter.", }), @@ -131,33 +131,39 @@ export type SchemaType = z.infer; const EducationReportChartSlide = ({ data }: { data: Partial }) => { - const slideData = Schema.parse(data); + const slideData = data; const chartHeightClass = slideData.showStatusMessage ? "h-[372px]" : "h-[486px]"; return ( -
+
-

+

{slideData.title}

-

+

{slideData.body}

-

+

{slideData.footnote}

-
-

+
+

{slideData.chartTitle}

-

+

{slideData.dateRange}

@@ -165,9 +171,9 @@ const EducationReportChartSlide = ({ data }: { data: Partial }) => {
diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx index ac5bc8fd..d491e193 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx @@ -23,7 +23,7 @@ const ServiceSchema = z.object({ tagline: z.string().min(3).max(12).meta({ description: "Short label under each service heading.", }), - body: z.string().min(40).max(90).meta({ + body: z.string().max(40).meta({ description: "Service description paragraph.", }), }); @@ -91,10 +91,16 @@ const EducationServicesSplitSlide = ({ data }: { data: Partial }) => return ( -
+
-

+

{title}

@@ -109,7 +115,10 @@ const EducationServicesSplitSlide = ({ data }: { data: Partial }) => {sections?.map((section, index) => (
}) =>
-

{section.heading}

-

+

{section.heading}

+

{section.tagline}

-

+

{section.body}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx index bd599daa..ec698ea3 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx @@ -51,16 +51,22 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) = return ( -
+
-
+
-

+

{data.title}

-

+

{data.description}

@@ -71,12 +77,12 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) =
-

+

{stat?.value}

-

+

{stat?.label}

@@ -88,12 +94,12 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) =
-

+

{stat.value}

-

+

{stat.label}

@@ -112,12 +118,12 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) =
-

+

{stat?.value}

-

+

{stat?.label}

@@ -131,12 +137,12 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) =
-

+

{stat.value}

-

+

{stat.label}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx index e6e18552..f7d900b2 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx @@ -47,26 +47,32 @@ export type SchemaType = z.infer; const EducationTableOfContentsSlide = ({ data }: { data: Partial }) => { return ( -
+
-
-

+
+

{data.titleLine1}
{data.titleLine2}

-
+
{data.items?.map((item, index) => (
- + {item.number} - + {item.label}
diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx index 0871cf62..86b1fcc8 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx @@ -49,9 +49,15 @@ const EducationTimelineSlide = ({ data }: { data: Partial }) => { const isSixOrLess = milestones?.length && milestones?.length <= 6; return ( -
+
-

+

{title}

@@ -82,13 +88,13 @@ function TimelineUpToSix({
-
- {index !== milestones.length - 1 &&
} +
+ {index !== milestones.length - 1 &&
}
-

+

{milestone.year}

-

+

{milestone.description}

@@ -114,10 +120,10 @@ function TimelineMoreThanSix({ - + {/* bottom horizontal line */} - {/*
*/} + {/*
*/}
-
- {index !== milestones.length - 1 &&
} +
+ {index !== milestones.length - 1 &&
}
-

+

{milestone.year}

-

+

{milestone.description}

@@ -147,7 +153,7 @@ function TimelineMoreThanSix({ {/* bottom row */}
@@ -158,16 +164,16 @@ function TimelineMoreThanSix({ return (
- {/* {colIndex === 0 &&
} */} - {true &&
} -
+ {/* {colIndex === 0 &&
} */} +
+
-

+

{item.year}

-

+

{item.description}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx index 1258be58..378f8d1a 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx @@ -11,7 +11,7 @@ const CardSchema = z.object({ heading: z.string().min(4).max(12).meta({ description: "Card heading for one challenge column.", }), - body: z.string().min(30).max(60).meta({ + body: z.string().max(40).meta({ description: "Card body copy for one challenge column.", }), dark: z.boolean().default(false).meta({ @@ -26,7 +26,7 @@ export const Schema = z.object({ taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ description: "Short label above the left-side paragraph.", }), - taglineBody: z.string().min(40).max(100).default( + taglineBody: z.string().max(80).default( "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." ).meta({ description: "Supporting paragraph on the left side.", @@ -43,7 +43,7 @@ export const Schema = z.object({ }), cards: z .array(CardSchema) - .min(3) + .max(3) .default([ { @@ -75,12 +75,15 @@ const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) = return (

{title}

@@ -88,11 +91,16 @@ const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) =

{taglineLabel}

-

{taglineBody}

+

+ {taglineBody} +

@@ -109,17 +117,29 @@ const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) =

{card.heading}

{card.body}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx index b6cdce83..a3243257 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx @@ -9,7 +9,7 @@ const ChallengeSchema = z.object({ heading: z.string().min(4).max(12).meta({ description: "Short heading for a single challenge block.", }), - body: z.string().min(40).max(96).meta({ + body: z.string().max(40).meta({ description: "Description text for a single challenge block.", }), }); @@ -53,27 +53,38 @@ const BusinessChallengesGridSlide = ({ data }: { data: Partial }) => return (

{title}

{challenges?.map((challenge, index) => (
-

+

{challenge.heading}

-

{challenge.body}

+

+ {challenge.body} +

))}
diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx index 3a19af33..2987d3e0 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -19,10 +19,10 @@ const RowSchema = z.object({ }); export const Schema = z.object({ - title: z.string().min(8).max(32).default("Comparison Chart").meta({ + title: z.string().min(8).max(20).default("Comparison Chart").meta({ description: "Main heading shown above the table.", }), - subtitle: z.string().min(30).max(140).default( + subtitle: z.string().max(80).default( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt." ).meta({ description: "Short subtitle shown under the main heading.", @@ -37,7 +37,7 @@ export const Schema = z.object({ }), rows: z .array(RowSchema) - .min(3) + .max(3) .default([ { @@ -119,26 +119,47 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { return (

{title}

-

{subtitle}

+

+ {subtitle} +

-
+
{columns?.map((column, index) => (
{column}
@@ -156,16 +177,28 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { return (
-
+
{row.label}
{cells?.map((status, cellIndex) => (
}) = const { title, subtitle, columns, highlightedHeaderIndex, rows } = data; return ( -
+
-

+

{title}

-

+

{subtitle}

-
- - - +
+
+ + {columns?.map((column, index) => { const isHighlighted = index + 1 === highlightedHeaderIndex; return ( {rows?.map((row, rowIndex) => { const cells = [row.cell1, row.cell2, row.cell3, row.cell4]; + const isHighlighted = rowIndex + 1 === highlightedHeaderIndex; + return ( {cells?.map((cell, cellIndex) => ( diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx index 2073ac26..87557f15 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx @@ -44,9 +44,15 @@ const CoverSlide = ({ data }: { data: Partial }) => { return (
-
+
}) => { className="h-[42px] w-[171px] object-cover" /> -

+

{label}

@@ -84,7 +93,7 @@ const CoverSlide = ({ data }: { data: Partial }) => { style={{ height: "365px", background: - "linear-gradient(0deg, rgba(218, 225, 222, 0.00) 0%, #DAE1DE 80.33%)", + "linear-gradient(0deg, rgba(218, 225, 222, 0.00) 0%, var(--background-color,#DAE1DE) 80.33%)", }} />
diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx index 093d7e8e..f11e7ba5 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx @@ -7,10 +7,10 @@ export const slideLayoutDescription = "A gallery slide with a title and paragraph on the left and a five-image collage on the right and bottom."; export const Schema = z.object({ - title: z.string().min(5).max(16).default("Image Gallery").meta({ + title: z.string().max(12).default("Image Gallery").meta({ description: "Main gallery heading.", }), - description: z.string().min(50).max(130).default( + description: z.string().max(80).default( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." ).meta({ description: "Supporting paragraph shown under the title.", @@ -18,7 +18,7 @@ export const Schema = z.object({ topCenterImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1521737711867-e3b97375f902?auto=format&fit=crop&w=800&q=80"), - __image_prompt__: z.string().min(10).max(100).default("Design team discussing project board"), + __image_prompt__: z.string().max(80).default("Design team discussing project board"), }).default({ __image_url__: "https://images.unsplash.com/photo-1521737711867-e3b97375f902?auto=format&fit=crop&w=800&q=80", __image_prompt__: "Design team discussing project board", @@ -28,7 +28,7 @@ export const Schema = z.object({ topRightImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1455390582262-044cdead277a?auto=format&fit=crop&w=800&q=80"), - __image_prompt__: z.string().min(10).max(100).default("Creative desk with notebook and photos"), + __image_prompt__: z.string().max(80).default("Creative desk with notebook and photos"), }).default({ __image_url__: "https://images.unsplash.com/photo-1455390582262-044cdead277a?auto=format&fit=crop&w=800&q=80", @@ -39,7 +39,7 @@ export const Schema = z.object({ bottomWideImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1300&q=80"), - __image_prompt__: z.string().min(10).max(100).default("City skyline seen from office window"), + __image_prompt__: z.string().max(80).default("City skyline seen from office window"), }).default({ __image_url__: "https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1300&q=80", @@ -50,7 +50,7 @@ export const Schema = z.object({ bottomCenterImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1517048676732-d65bc937f952?auto=format&fit=crop&w=900&q=80"), - __image_prompt__: z.string().min(10).max(100).default("Art gallery wall with framed photos"), + __image_prompt__: z.string().max(80).default("Art gallery wall with framed photos"), }).default({ __image_url__: "https://images.unsplash.com/photo-1517048676732-d65bc937f952?auto=format&fit=crop&w=900&q=80", @@ -61,7 +61,7 @@ export const Schema = z.object({ bottomRightImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1521791136064-7986c2920216?auto=format&fit=crop&w=900&q=80"), - __image_prompt__: z.string().min(10).max(100).default("Office workshop with presentation board"), + __image_prompt__: z.string().max(80).default("Office workshop with presentation board"), }).default({ __image_url__: "https://images.unsplash.com/photo-1521791136064-7986c2920216?auto=format&fit=crop&w=900&q=80", @@ -87,18 +87,26 @@ const ImageGallerySlide = ({ data }: { data: Partial }) => { return (

{title}

-

{description}

+

+ {description} +

}) => { return (
-
+
{portraitImage?.__image_url__ && ( }) => {

{title}

@@ -83,10 +90,14 @@ const IntroductionSlide = ({ data }: { data: Partial }) => {

{block.label}

-

+

{block.body}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx index 705803fb..2c5d5ff0 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx @@ -7,10 +7,10 @@ export const slideLayoutDescription = "A KPI overview slide with a dark-tinted background image, large title, and a two-row grid of six white KPI cards."; const KpiSchema = z.object({ - value: z.string().min(1).max(8).meta({ + value: z.string().max(5).meta({ description: "Primary KPI value shown in a card.", }), - body: z.string().min(10).max(28).meta({ + body: z.string().max(20).meta({ description: "Short KPI supporting text.", }), }); @@ -64,7 +64,10 @@ const KpiCardsSlide = ({ data }: { data: Partial }) => { return (
{backgroundImage?.__image_url__ && ( }) => { /> )} -
+
-

{title}

@@ -86,8 +96,15 @@ const KpiCardsSlide = ({ data }: { data: Partial }) => {
{items?.map((item, index) => ( -
-
+
+
{kpiIcon?.__icon_query__} }) => {

{item.value}

-

+

{item.body}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx index 377c2b0e..07f887f2 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx @@ -45,12 +45,11 @@ export const Schema = z.object({ export type SchemaType = z.infer; - const COLORS = [ - "#5f7f79", - "#1f5a4f", - "#0d4f43", - "#06463d", + "var(--graph-0,#5f7f79)", + "var(--graph-1,#1f5a4f)", + "var(--graph-2,#0d4f43)", + "var(--graph-3,#06463d)", ]; const MarketOpportunitySlide = ({ data }: { data: Partial }) => { @@ -59,25 +58,47 @@ const MarketOpportunitySlide = ({ data }: { data: Partial }) => { return (

{title}

-

{subtitle}

+

+ {subtitle} +

{bullets?.map((bullet, index) => (
- -

{bullet.text}

- - + +

+ {bullet.text} +

+ +
))}
@@ -88,14 +109,17 @@ const MarketOpportunitySlide = ({ data }: { data: Partial }) => { key={index} className="absolute rounded-full" style={{ - width: 237 + (index * 50), - height: 237 + (index * 50), + width: 237 + index * 50, + height: 237 + index * 50, bottom: 0, right: 0, backgroundColor: COLORS[index], }} > -

+

{value}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx index 5922c14d..7094003a 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx @@ -34,14 +34,14 @@ export const Schema = z.object({ taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ description: "Small heading above team description.", }), - taglineBody: z.string().min(50).max(100).default( + taglineBody: z.string().max(70).default( "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." ).meta({ description: "Short descriptive paragraph at top-right.", }), members: z .array(MemberSchema) - .min(4) + .max(4) .default([ { @@ -94,12 +94,15 @@ const MeetTeamSlide = ({ data }: { data: Partial }) => { return (

{title}

@@ -107,11 +110,16 @@ const MeetTeamSlide = ({ data }: { data: Partial }) => {

{taglineLabel}

-

{taglineBody}

+

+ {taglineBody} +

@@ -125,17 +133,29 @@ const MeetTeamSlide = ({ data }: { data: Partial }) => { />

{member.title}

{member.name}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx index 7b94a574..ea7fd434 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx @@ -46,31 +46,60 @@ const MissionVisionSlide = ({ data }: { data: Partial }) => { return (

{title}

-
-

+

+

{missionLabel}

-

{missionBody}

+

+ {missionBody} +

-
-

+

+

{visionLabel}

-

{visionBody}

+

+ {visionBody} +

-
+
{image?.__image_url__ && ( }) => { return (

{title}

@@ -78,14 +81,22 @@ const OurServicesSlide = ({ data }: { data: Partial }) => {

{taglineLabel}

-

{taglineBody}

+

+ {taglineBody} +

-
+
{featureImage?.__image_url__ && ( }) => {

{service.heading}

{service.body}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx index fd24e29a..e9a4a4dd 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx @@ -10,12 +10,12 @@ const PlanSchema = z.object({ price: z.string().min(4).max(12).meta({ description: "Plan price label shown at the top of each card.", }), - description: z.string().min(18).max(26).meta({ + description: z.string().max(20).meta({ description: "Short statement describing the plan.", }), features: z - .array(z.string().min(10).max(24)) - .min(4) + .array(z.string().max(18)) + .max(4) .meta({ description: "Four bullet features shown in the pricing card.", @@ -41,7 +41,6 @@ export const Schema = z.object({ }), plans: z .array(PlanSchema) - .min(3) .max(3) .default([ { @@ -91,12 +90,15 @@ const PricingPlanSlide = ({ data }: { data: Partial }) => { return (

{title}

@@ -109,17 +111,29 @@ const PricingPlanSlide = ({ data }: { data: Partial }) => {

{plan.price}

{plan.description}

@@ -135,7 +149,11 @@ const PricingPlanSlide = ({ data }: { data: Partial }) => { />

{feature}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx index 8e57213f..5b1cae5f 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx @@ -80,12 +80,15 @@ const ProcessSlide = ({ data }: { data: Partial }) => { return (

{title}

@@ -110,23 +113,28 @@ const ProcessSlide = ({ data }: { data: Partial }) => { }} /> - +
- +

{step.label}

-

{step.body}

+

+ {step.body} +

) @@ -141,23 +149,28 @@ const ProcessSlide = ({ data }: { data: Partial }) => {

{step.label}

-

{step.body}

+

+ {step.body} +

- + {step.icon.__icon_query__}
- +
diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx index e980999c..3b564c5b 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx @@ -229,17 +229,21 @@ export const Schema = z.object({ export type SchemaType = z.infer; -const MINI_BAR_DARK = "#0B4B40"; -const MINI_BAR_LIGHT = "#CED3D1"; -const DONUT_COLORS = ["#0B4B40", "#4B6B61", "#7B938C"]; -const KPI_ICON_BG = "#063C73"; +const MINI_BAR_DARK = "var(--graph-0,#0B4B40)"; +const MINI_BAR_LIGHT = "var(--graph-1,#CED3D1)"; +const DONUT_COLORS = [ + "var(--graph-0,#0B4B40)", + "var(--graph-1,#4B6B61)", + "var(--graph-2,#7B938C)", +]; +const KPI_ICON_BG = "var(--graph-3,#063C73)"; const PulseIcon = () => (
{sideImage?.__image_url__ && ( }) => { /> )} -
+

{title}

-

+

{taglineLabel}

-

+

{taglineBody}

@@ -350,8 +374,14 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => {
-

{chartTitle}

+

+ {chartTitle} +

{activeChartStyle === "mini-bars" && ( <> @@ -362,7 +392,7 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { margin={{ top: 0, right: 8, left: -6, bottom: 0 }} barCategoryGap={16} > - + }) => { tickFormatter={(value) => `$${value}`} axisLine={false} tickLine={false} - tick={{ fill: "#6C7271", fontSize: 10 }} + tick={{ fill: "var(--background-text,#6C7271)", fontSize: 10 }} /> }) => {
-

{footerLabel}

-

{footerValue}

+

+ {footerLabel} +

+

+ {footerValue} +

)} @@ -431,11 +471,19 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { className="h-[14px] w-[14px] rounded-full" style={{ backgroundColor: DONUT_COLORS[index % DONUT_COLORS.length] }} /> -

+

{legendLabels?.[index] ?? entry.name}

-

{percent}%

+

+ {percent}% +

); })} @@ -452,18 +500,18 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { margin={{ top: 12, right: 6, left: -12, bottom: 0 }} barCategoryGap={20} > - + }) => { maxBarSize={20} isAnimationActive={false} > - + - + @@ -489,12 +547,28 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => {
- -

{legendLabels?.[0] ?? "Option A"}

+ +

+ {legendLabels?.[0] ?? "Option A"} +

- -

{legendLabels?.[1] ?? "Option B"}

+ +

+ {legendLabels?.[1] ?? "Option B"} +

@@ -505,20 +579,33 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => {
- + }) => { }) => {
- -

{legendLabels?.[0] ?? "Option A"}

+ +

+ {legendLabels?.[0] ?? "Option A"} +

- -

{legendLabels?.[1] ?? "Option B"}

+ +

+ {legendLabels?.[1] ?? "Option B"} +

@@ -560,9 +663,16 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { >
{visibleMetricCards.map((metric, index) => ( -
+
-
+
{usePulseFallback ? ( ) : ( @@ -574,9 +684,19 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { /> )}
-

{metric.value}

+

+ {metric.value} +

-

{metric.body}

+

+ {metric.body} +

))}
diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx index 5cdba0a3..deb4f2ec 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx @@ -1,7 +1,7 @@ import * as z from "zod"; -const PRODUCT_BG = "#d7dddd"; -const PRODUCT_DARK = "#05463d"; +const PRODUCT_BG = "var(--background-color,#d7dddd)"; +const PRODUCT_DARK = "var(--primary-color,#05463d)"; export const slideLayoutId = "product-overview-table-of-content-slide"; @@ -55,7 +55,10 @@ const TableOfContentSlide = ({ data }: { data: Partial }) => { return (
@@ -64,12 +67,25 @@ const TableOfContentSlide = ({ data }: { data: Partial }) => {
-

+

{section.title}

-

{section.description}

+

+ {section.description} +

-

{section.number}

+

+ {section.number} +

))}
@@ -78,11 +94,14 @@ const TableOfContentSlide = ({ data }: { data: Partial }) => {

{title}

-

+

{description}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx index f1cc57b2..cd98a65c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx @@ -77,11 +77,25 @@ const DataAnalysisBarSlide = ({ data }: { data: Partial }) => { const series = chartData?.series ?? []; return ( -
-
+
+
-

{title}

+

+ {title} +

@@ -89,7 +103,13 @@ const DataAnalysisBarSlide = ({ data }: { data: Partial }) => { {items?.map((item, index) => (
-
+
{itemIcon?.__icon_query__} }) => { style={{ filter: "brightness(0) invert(1)" }} />
-

{item.title}

+

+ {item.title} +

-

{item.description}

+

+ {item.description} +

))}
@@ -110,8 +140,14 @@ const DataAnalysisBarSlide = ({ data }: { data: Partial }) => {
-
- +
+

{legendLabel}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx index 02fc5580..b1743e96 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx @@ -141,7 +141,13 @@ function SummaryCard({ }) { return (
-
+
{iconAlt
-

{value}

-

{label}

+

+ {value} +

+

+ {label} +

); @@ -167,16 +183,32 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial }) => const otherHalfChart = charts?.slice(Math.ceil(charts.length / 2)); return ( -
-
+
+
-

{title}

+

+ {title} +

{summaryCards && summaryCards.length > 0 &&
+ style={{ + gridTemplateColumns: `repeat(${summaryCards.length}, minmax(220px, 1fr))`, + backgroundColor: "var(--card-color,#ffffff)", + }}> {summaryCards?.map((card, index) => ( }) => {halfChart && halfChart.length > 0 &&
{halfChart?.map((chart, index) => (
}) => {otherHalfChart && otherHalfChart.length > 0 &&
{otherHalfChart?.map((chart, index) => (
-
+
+
-

{data.title}

+

+ {data.title} +

-
+
{data.insightIcon?.__icon_query__}
-

{data.insightBody}

+

+ {data.insightBody} +

-
- +
+

{data.legendLabel}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx index e946fec1..3098795d 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx @@ -88,18 +88,32 @@ type StatMetric = { function StatPill({ metrics }: { metrics: StatMetric[] }) { return ( -
+
{metrics.map((metric, index) => (

{metric.value}

{metric.label}

-

{metric.description}

+

+ {metric.description} +

{index === 0 && (
- +
)} @@ -116,22 +130,39 @@ const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => const series = chartData?.series ?? []; return ( -
-
+
+
-

{title}

+

+ {title} +

-
+
- + {seriesALabel} - + {seriesBLabel}
@@ -143,13 +174,20 @@ const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => data={rows} series={series} colorFallback="#157CFF" - dualLineColors={["#9fb6ff", "#4d4ef3"]} + density="default" + dualLineColors={["var(--graph-0,#9fb6ff)", "var(--graph-1,#4d4ef3)"]} />
-
- +
+

{data.legendLabel}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx index 5f09fad3..26260404 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx @@ -52,14 +52,23 @@ const DataAnalysisListSlide = ({ data }: { data: Partial }) => { const { title, itemIcon, items } = data; return ( -
+
-

+

{title}

@@ -68,7 +77,13 @@ const DataAnalysisListSlide = ({ data }: { data: Partial }) => { {items?.map((item, index) => (
-
+
{itemIcon?.__icon_query__} }) => { style={{ filter: "brightness(0) invert(1)" }} />
-

+

{item.title}

-

+

{item.description}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx index 1af15451..88f87f8e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx @@ -15,24 +15,43 @@ const IntroSlide = ({ data }: { data: Partial }) => { const { title, subtitle, name, position } = data; return ( -
+
- - + + - - + +
-

{title}

-

{subtitle}

+

+ {title} +

+

+ {subtitle} +

-
+
-

{name}

-

{position}

+

{name}

+

{position}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx index 327e7f8a..16eb8267 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx @@ -45,25 +45,37 @@ const IntroductionImageSlide = ({ data }: { data: Partial }) => { const { title, body, bullets, featureImage } = data; return ( -
+
-

+

{title}

-

+

{body}

-
    +
      {bullets?.map((bullet, index) => (
    • {bullet} @@ -73,7 +85,10 @@ const IntroductionImageSlide = ({ data }: { data: Partial }) => {
-
+
{featureImage?.__image_prompt__} +
{metrics.map((metric, index) => ( @@ -99,14 +105,20 @@ function StatPill({ {metric.value}

{metric.label}

-

+

{metric.description}

{index === 0 &&
- +
} @@ -123,25 +135,37 @@ const IntroductionStatsSlide = ({ data }: { data: Partial }) => { const { title, body, bullets, statColumns } = data; return ( -
+
-

+

{title}

-

+

{body}

-
    +
      {bullets?.map((bullet, index) => (
    • {bullet} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx index 94470948..530a0391 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx @@ -66,14 +66,23 @@ const MilestoneSlide = ({ data }: { data: Partial }) => { const { title, activeIndex, items } = data; return ( -
      +
      -

      +

      {title}

      @@ -91,17 +100,29 @@ const MilestoneSlide = ({ data }: { data: Partial }) => { ? "z-10 bg-[#157CFF] text-white" : "border border-[#157CFF] bg-white text-[#157CFE]" } ${index > 0 ? "ml-[-45px]" : ""} `} + style={{ + backgroundColor: isActive ? "var(--primary-color,#157CFF)" : "var(--card-color,#ffffff)", + borderColor: "var(--primary-color,#157CFF)", + color: isActive ? "var(--primary-text,#ffffff)" : "var(--primary-color,#157CFE)", + }} > - + {item.stepNumber}
      -
      0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`}> -

      +
      0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`} + style={{ color: "var(--background-text,#232223)" }} + > +

      {item.heading}

      -

      +

      {item.description}

      diff --git a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx index fa2902b3..a9aa00a0 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx @@ -80,7 +80,13 @@ function StatPill({ return ( -
      +
      {metrics.map((metric, index) => ( @@ -92,14 +98,20 @@ function StatPill({ {metric.value}

      {metric.label}

      -

      +

      {metric.description}

      {index === 0 &&
      - +
      } @@ -115,14 +127,23 @@ const PerformanceSnapshotSlide = ({ data }: { data: Partial }) => { const { title, columns } = data; return ( -
      +
      -

      +

      {title}

      diff --git a/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx index 9df93cdb..bb471e15 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx @@ -108,14 +108,23 @@ const ServicesSlide = ({ data }: { data: Partial }) => { const { title, activeIndex, items } = data; return ( -
      +
      -

      +

      {title}

      @@ -135,6 +144,9 @@ const ServicesSlide = ({ data }: { data: Partial }) => { 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', + backgroundColor: isActive ? "var(--primary-color,#157CFF)" : "transparent", + borderColor: "var(--primary-color,#157CFF)", + color: isActive ? "var(--primary-text,#ffffff)" : "var(--primary-color,#157CFE)", }} > }) => { />
      -

      {item.heading}

      -

      {item.description} @@ -194,7 +210,10 @@ const ServicesSlide = ({ data }: { data: Partial }) => { width: items?.length === 3 ? '117px' : items?.length === 4 ? '84px' : items?.length === 5 ? '60px' : '112px', }} height="15" viewBox="0 0 119 15" fill="none"> - +

      )} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx index 8e881bce..2d75e8ee 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx @@ -70,7 +70,13 @@ function SolutionCard({ description: string; }) { return ( -
      +

      {stepNumber}

      {description} @@ -84,14 +90,23 @@ const SolutionSlide = ({ data }: SolutionSlideProps) => { const visibleCards = showImage ? cards?.slice(0, 2) : cards; return ( -

      +
      {title && ( -

      +

      {title}

      )} @@ -100,7 +115,10 @@ const SolutionSlide = ({ data }: SolutionSlideProps) => { {showImage ? (
      {featureImage?.__image_url__ && ( -
      +
      {featureImage?.__image_prompt__}; const TeamSlide = ({ data }: { data: Partial }) => { return ( -
      +
      @@ -93,9 +99,19 @@ const TeamSlide = ({ data }: { data: Partial }) => { alt={member.image.__image_prompt__} className="h-full w-full object-cover" /> -
      -
      -

      {member.title}

      +
      +
      +

      + {member.title} +

      {member.name}

      diff --git a/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx b/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx index 75be9b1c..7c901f26 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/flexibleReportChart.tsx @@ -249,7 +249,7 @@ export function normalizeFlexibleChartData( return { data: mapped, series }; } -const graphVar = (index: number, fallback: string) => `var(--graph-${index % 8}, ${fallback})`; +const graphVar = (index: number, fallback: string) => `var(--graph-${index % 10}, ${fallback})`; export type ChartDensity = "default" | "compact"; @@ -269,7 +269,7 @@ export function FlexibleReportChart({ data: chartData, series = [], colorFallback = "#157CFF", - dualLineColors = ["#9fb6ff", "#4d4ef3"], + dualLineColors = ["var(--graph-0,#9fb6ff)", "var(--graph-1,#4d4ef3)"], density = "default", }: FlexibleReportChartProps) { const areaGradientId = `flex-area-${useId().replace(/:/g, "")}`; @@ -352,25 +352,24 @@ export function FlexibleReportChart({ const nm = String(name ?? ""); const short = nm.length <= ui.pieMaxNameLen; const pct = `${(percent * 100).toFixed(0)}%`; - const fontSize = compact ? (short ? 6 : 5) : short ? 10 : 9; + const fontSize = compact ? (short ? 6 : 5) : short ? 12 : 12; const labelText = compact ? pct : short ? `${name} ${pct}` : pct; return ( - - {labelText} - + + + {/* */} + + {labelText} + + ); }; @@ -399,7 +398,7 @@ export function FlexibleReportChart({ @@ -425,7 +424,13 @@ export function FlexibleReportChart({ axisLine={false} /> - + @@ -629,7 +634,7 @@ export function FlexibleReportChart({ tickLine={false} axisLine={false} /> - + diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index d2783b16..9a40bcee 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -36,7 +36,7 @@ import CoverSlide, { Schema as PoCoverSchema, slideLayoutId as PoCoverId, slideL import ImageGallerySlide, { Schema as PoImageGallerySchema, slideLayoutId as PoImageGalleryId, slideLayoutName as PoImageGalleryName, slideLayoutDescription as PoImageGalleryDesc } from "./ProductOverview/ImageGallerySlide"; import IntroductionSlide, { Schema as PoIntroductionSchema, slideLayoutId as PoIntroductionId, slideLayoutName as PoIntroductionName, slideLayoutDescription as PoIntroductionDesc } from "./ProductOverview/IntroductionSlide"; import KpiCardsSlide, { Schema as PoKpiCardsSchema, slideLayoutId as PoKpiCardsId, slideLayoutName as PoKpiCardsName, slideLayoutDescription as PoKpiCardsDesc } from "./ProductOverview/KpiCardsSlide"; -import MarketOpportunitySlide, { Schema as PoMarketOpportunitySchema, slideLayoutId as PoMarketOpportunityId, slideLayoutName as PoMarketOpportunityName, slideLayoutDescription as PoMarketOpportunityDesc } from "./ProductOverview/MarketOpportunitySlide"; +// import MarketOpportunitySlide, { Schema as PoMarketOpportunitySchema, slideLayoutId as PoMarketOpportunityId, slideLayoutName as PoMarketOpportunityName, slideLayoutDescription as PoMarketOpportunityDesc } from "./ProductOverview/MarketOpportunitySlide"; import MeetTeamSlide, { Schema as PoMeetTeamSchema, slideLayoutId as PoMeetTeamId, slideLayoutName as PoMeetTeamName, slideLayoutDescription as PoMeetTeamDesc } from "./ProductOverview/MeetTeamSlide"; import MissionVisionSlide, { Schema as PoMissionVisionSchema, slideLayoutId as PoMissionVisionId, slideLayoutName as PoMissionVisionName, slideLayoutDescription as PoMissionVisionDesc } from "./ProductOverview/MissionVisionSlide"; import OurServicesSlide, { Schema as PoOurServicesSchema, slideLayoutId as PoOurServicesId, slideLayoutName as PoOurServicesName, slideLayoutDescription as PoOurServicesDesc } from "./ProductOverview/OurServicesSlide"; @@ -274,7 +274,7 @@ export const productOverviewTemplates: TemplateWithData[] = [ createTemplateEntry(TableOfContentSlide, PoTableOfContentSchema, PoTableOfContentId, PoTableOfContentName, PoTableOfContentDesc, "product-overview", "TableOfContentSlide"), createTemplateEntry(IntroductionSlide, PoIntroductionSchema, PoIntroductionId, PoIntroductionName, PoIntroductionDesc, "product-overview", "IntroductionSlide"), createTemplateEntry(MissionVisionSlide, PoMissionVisionSchema, PoMissionVisionId, PoMissionVisionName, PoMissionVisionDesc, "product-overview", "MissionVisionSlide"), - createTemplateEntry(MarketOpportunitySlide, PoMarketOpportunitySchema, PoMarketOpportunityId, PoMarketOpportunityName, PoMarketOpportunityDesc, "product-overview", "MarketOpportunitySlide"), + // createTemplateEntry(MarketOpportunitySlide, PoMarketOpportunitySchema, PoMarketOpportunityId, PoMarketOpportunityName, PoMarketOpportunityDesc, "product-overview", "MarketOpportunitySlide"), createTemplateEntry(BusinessChallengesGridSlide, PoBizChallengesGridSchema, PoBizChallengesGridId, PoBizChallengesGridName, PoBizChallengesGridDesc, "product-overview", "BusinessChallengesGridSlide"), createTemplateEntry(BusinessChallengesCardsSlide, PoBizChallengesCardsSchema, PoBizChallengesCardsId, PoBizChallengesCardsName, PoBizChallengesCardsDesc, "product-overview", "BusinessChallengesCardsSlide"), createTemplateEntry(OurServicesSlide, PoOurServicesSchema, PoOurServicesId, PoOurServicesName, PoOurServicesDesc, "product-overview", "OurServicesSlide"), From de290281eb1f22b1d46fc9924bd1d03c122dca70 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Thu, 9 Apr 2026 13:09:49 +0545 Subject: [PATCH 07/18] feat: font support in new templates --- electron/servers/nextjs/app/globals.css | 34 + .../Code/CodeSlide01RoadmapCover.tsx | 4 +- .../Code/CodeSlide02CodeExplanationSplit.tsx | 93 +-- .../Code/CodeSlide03ApiRequestResponse.tsx | 139 ++-- .../Code/CodeSlide04FeatureGrid.tsx | 103 +-- .../Code/CodeSlide05ComparisonTable.tsx | 123 ++-- .../Code/CodeSlide06Workflow.tsx | 111 +-- .../Code/CodeSlide07UseCaseList.tsx | 91 +-- .../Code/CodeSlide08CodeExplanationText.tsx | 45 +- .../Code/CodeSlide09TableOfContent.tsx | 51 +- .../Code/CodeSlide10MetricsSplit.tsx | 85 +-- .../Code/CodeSlide11MetricsGrid.tsx | 79 +- .../Education/EducationContentSplitSlide.tsx | 10 +- .../EducationStatisticsGridSlide.tsx | 14 +- .../BusinessChallengesCardsSlide.tsx | 127 ++-- .../BusinessChallengesGridSlide.tsx | 73 +- .../ProductOverview/ComparisonChartSlide.tsx | 163 ++-- .../ComparisonTableWithTextSlide.tsx | 147 ++-- .../ProductOverview/CoverSlide.tsx | 107 +-- .../ProductOverview/ImageGallerySlide.tsx | 99 +-- .../ProductOverview/IntroductionSlide.tsx | 95 +-- .../ProductOverview/KpiCardsSlide.tsx | 115 +-- .../MarketOpportunitySlide.tsx | 137 ++-- .../ProductOverview/MeetTeamSlide.tsx | 139 ++-- .../ProductOverview/MissionVisionSlide.tsx | 121 +-- .../ProductOverview/OurServicesSlide.tsx | 155 ++-- .../ProductOverview/PricingPlanSlide.tsx | 143 ++-- .../ProductOverview/ProcessSlide.tsx | 205 +++--- .../ProductOverview/ReportSnapshotSlide.tsx | 693 +++++++++--------- .../ProductOverview/TableOfContentSlide.tsx | 93 +-- .../Report/DataAnalysisBarSlide.tsx | 10 +- .../Report/DataAnalysisDashboardSlide.tsx | 19 +- .../Report/DataAnalysisInsightBarSlide.tsx | 5 +- .../Report/DataAnalysisLineStatsSlide.tsx | 29 +- .../Report/DataAnalysisListSlide.tsx | 2 +- .../Report/IntroSlide.tsx | 2 +- .../Report/IntroductionImageSlide.tsx | 14 +- .../Report/IntroductionStatsSlide.tsx | 30 +- .../Report/MilestoneSlide.tsx | 2 +- .../Report/PerformanceSnapshotSlide.tsx | 24 +- .../Report/ServicesSlide.tsx | 2 +- .../Report/SolutionSlide.tsx | 2 +- .../Report/TeamSlide.tsx | 2 +- .../Report/flexibleReportChart.tsx | 4 +- 44 files changed, 1925 insertions(+), 1816 deletions(-) diff --git a/electron/servers/nextjs/app/globals.css b/electron/servers/nextjs/app/globals.css index ac589850..137382cc 100644 --- a/electron/servers/nextjs/app/globals.css +++ b/electron/servers/nextjs/app/globals.css @@ -82,6 +82,40 @@ strong { } +@font-face { + font-family: 'Times New Roman'; + src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/times.ttf') format('truetype'); + font-style: normal; + font-display: swap; + +} + +@font-face { + font-family: 'Helvetica Neue'; + src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeueBold.otf') format('truetype'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Helvetica Neue'; + src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeueMedium.otf') format('truetype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Helvetica Neue'; + src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeueRoman.otf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + + + ::selection { background-color: hsl(var(--chart-1)); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx index 7c0ff024..4e100bca 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx @@ -31,7 +31,8 @@ export type SchemaType = z.infer; const CodeSlide01RoadmapCover = ({ data }: { data: Partial }) => { - return ( + return (<> +
      }) => { {data.pageLabel}
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx index eda51568..cef31779 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx @@ -64,63 +64,66 @@ const CodeSlide02CodeExplanationSplit = ({ }) => { return ( -
      + <> + +
      -
      -

      {data.title}

      +
      +

      {data.title}

      -
      -
      -

      +

      - {data.codeSnippet?.fileName} -

      -
      +              

      + {data.codeSnippet?.fileName} +

      +
       
      -              
      -                {data.codeSnippet?.content}
      -              
      -            
      -
      + + {data.codeSnippet?.content} + + +
      -
      -

      {data.explanationTitle}

      -

      - {data.explanation} -

      +
      +

      {data.explanationTitle}

      +

      + {data.explanation} +

      +
      -
      -
      - {data.pageLabel} +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx index 3c8c74d6..784b30a7 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx @@ -73,43 +73,71 @@ const CodeSlide03ApiRequestResponse = ({ }) => { return ( -
      + <> + +
      -
      -

      {data.title}

      +
      +

      {data.title}

      -
      -
      -
      -
      +
      +
      +
      +
      +

      + {data.method} +

      +

      {data.endpoint}

      +
      +

      Headers

      +
      + {data.headers?.map((item) => ( +

      {item}

      + ))} +
      +
      + +

      - {data.method} + {data.requestSnippet?.fileName}

      -

      {data.endpoint}

      -
      -

      Headers

      -
      - {data.headers?.map((item) => ( -

      {item}

      - ))} +
      +
      +                  
      +                    {data.requestSnippet?.content}
      +                  
      +                
      @@ -128,55 +156,30 @@ const CodeSlide03ApiRequestResponse = ({ borderColor: "var(--stroke,#1D293D80)", }} > - {data.requestSnippet?.fileName} + {data.responseSnippet?.fileName}

       
                       
      -                  {data.requestSnippet?.content}
      +                  {data.responseSnippet?.content}
                       
                     
      +
      -
      -

      - {data.responseSnippet?.fileName} -

      -
      -
      -              
      -                {data.responseSnippet?.content}
      -              
      -            
      -
      +
      + {data.pageLabel}
      - -
      - {data.pageLabel} -
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx index 5062cd8c..6416862c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx @@ -99,61 +99,64 @@ const CodeSlide04FeatureGrid = ({ data }: { data: Partial }) => { return ( -
      - - -

      {data.title}

      - -
      - {data?.features?.map((feature) => ( -
      -
      -

      {feature.title}

      - - {feature.icon.__icon_query__} - -
      -

      {feature.description}

      -
      - ))} -
      - - + <> +
      - {data.pageLabel} + + +

      {data.title}

      + +
      + {data?.features?.map((feature) => ( +
      +
      +

      {feature.title}

      + + {feature.icon.__icon_query__} + +
      +

      {feature.description}

      +
      + ))} +
      + + +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx index 0dbd5388..587c2615 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx @@ -50,7 +50,7 @@ export const Schema = z.object({ export type SchemaType = z.infer; function renderCell(value: string) { - if (value.toLowerCase() === "check") { + if (value && value.toLowerCase() === "check") { return ; } @@ -60,83 +60,86 @@ function renderCell(value: string) { const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) => { return ( -
      - -

      {data.title}

      - + <> +
      + +

      {data.title}

      +
      +
      - {data?.tableColumns?.map((column) => ( -

      - {column} -

      - ))} -
      - -
      - {data?.rows?.map((row) => ( -
      + {data?.tableColumns?.map((column) => (

      - {row.feature} + {column}

      -
      {renderCell(row.column1)}
      -
      {renderCell(row.column2)}
      -
      {renderCell(row.column3)}
      -
      - ))} + ))} +
      + +
      + {data?.rows?.map((row) => ( +
      +

      + {row.feature} +

      +
      {renderCell(row.column1)}
      +
      {renderCell(row.column2)}
      +
      {renderCell(row.column3)}
      +
      + ))} +
      +
      +
      + {data.pageLabel} +
      - -
      - {data.pageLabel} -
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx index 2b90ee1a..765de291 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx @@ -81,65 +81,68 @@ export type SchemaType = z.infer; const CodeSlide06Workflow = ({ data }: { data: Partial }) => { return ( -
      - -

      {data.title}

      - -
      - {data?.steps?.map((step, index) => ( - -
      -
      - {step.icon.__icon_query__} -
      -

      {step.title}

      -

      {step.description}

      -
      - {index < (data?.steps?.length || 0) - 1 && ( - - - - - )} -
      - ))} - -
      - + <> +
      - {data.pageLabel} + +

      {data.title}

      + +
      + {data?.steps?.map((step, index) => ( + +
      +
      + {step.icon.__icon_query__} +
      +

      {step.title}

      +

      {step.description}

      +
      + {index < (data?.steps?.length || 0) - 1 && ( + + + + + )} +
      + ))} + +
      + +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx index 8d0f764e..5defbc5d 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx @@ -36,55 +36,58 @@ export type SchemaType = z.infer; const CodeSlide07UseCaseList = ({ data }: { data: Partial }) => { return ( -
      - - -

      {data.title}

      - -
      - {data?.items?.map((item, index) => ( -
      - - {index + 1} - -

      {item}

      -
      - ))} -
      - - + <> +
      - {data.pageLabel} + + +

      {data.title}

      + +
      + {data?.items?.map((item, index) => ( +
      + + {index + 1} + +

      {item}

      +
      + ))} +
      + + +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx index 871d2155..b4e3831c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx @@ -32,32 +32,35 @@ export type SchemaType = z.infer; const CodeSlide08CodeExplanationText = ({ data }: { data: Partial }) => { return ( -
      - - -

      {data.title}

      -
      -

      {data.explanationTitle}

      -

      {data.explanation}

      -
      - + <> +
      - {data.pageLabel} + + +

      {data.title}

      +
      +

      {data.explanationTitle}

      +

      {data.explanation}

      +
      + +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx index a1aebc91..f37d0bea 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx @@ -72,35 +72,38 @@ const CodeSlide09TableOfContent = ({ data }: { data: Partial }) => { return ( -
      - - -
      -

      {data.title}

      - -
      - - -
      -
      - + <> +
      - {data.pageLabel} + + +
      +

      {data.title}

      + +
      + + +
      +
      + +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx index 7cfeda87..e41b3dd8 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx @@ -55,52 +55,55 @@ const CodeSlide10MetricsSplit = ({ data }: { data: Partial }) => { return ( -
      - - -

      {data.title}

      -
      -
      -

      {data.explanationTitle}

      -

      {data.explanation}

      -
      - -
      - {data?.metrics?.map((metric, index) => ( -
      -

      {metric.value}

      -

      {metric.label}

      -

      {metric.period}

      -
      - ))} -
      -
      - + <> +
      - {data?.pageLabel} + + +

      {data.title}

      +
      +
      +

      {data.explanationTitle}

      +

      {data.explanation}

      +
      + +
      + {data?.metrics?.map((metric, index) => ( +
      +

      {metric.value}

      +

      {metric.label}

      +

      {metric.period}

      +
      + ))} +
      +
      + +
      + {data?.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx index 5e9761cc..ecce205a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx @@ -46,49 +46,52 @@ export type SchemaType = z.infer; const CodeSlide11MetricsGrid = ({ data }: { data: Partial }) => { return ( -
      - - - -

      {data.title}

      - -
      - {data?.metrics?.map((metric, index) => ( -
      -

      {metric.value}

      -

      {metric.label}

      -

      {metric.period}

      -
      - ))} -
      - - + <> +
      - {data.pageLabel} + + + +

      {data.title}

      + +
      + {data?.metrics?.map((metric, index) => ( +
      +

      {metric.value}

      +

      {metric.label}

      +

      {metric.period}

      +
      + ))} +
      + + +
      + {data.pageLabel} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx index 500039c5..e12bb2a3 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx @@ -52,16 +52,16 @@ const EducationContentSplitSlide = ({ data }: { data: Partial }) => className=" h-full w-full object-cover object-center" />
      -
      -
      +
      {collageImage?.__image_prompt__}
      -
      {collageImage?.__image_prompt__}
      diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx index ec698ea3..eefd401e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx @@ -7,21 +7,21 @@ export const slideLayoutDescription = const StatisticSchema = z.object({ value: z.string().min(1).max(8).meta({ - description: "Main metric value shown at the top of one card.", + description: "Main metric value shown at the top of one card. with max 8 characters", }), - label: z.string().min(3).max(22).meta({ - description: "Label shown under the value.", + label: z.string().min(3).max(20).meta({ + description: "Label shown under the value. with max 20 characters", }), }); export const Schema = z.object({ title: z.string().min(4).max(14).default("Statistics").meta({ - description: "Main title shown in the left column.", + description: "Main title shown in the left column. with max 14 characters", }), description: z.string().min(40).max(120).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. with max 120 characters" ).meta({ - description: "Supporting line shown under the left title.", + description: "Supporting line shown under the left title. with max 120 characters", }), stats: z .array(StatisticSchema) @@ -38,7 +38,7 @@ export const Schema = z.object({ { value: "20", label: "Merchandising Team" }, ]) .meta({ - description: "Eight statistic cards shown in a 2-column, 4-row grid.", + description: "Eight statistic cards. with max 8 cards", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx index 378f8d1a..2d3e2191 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx @@ -73,80 +73,83 @@ const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) = const { title, taglineLabel, taglineBody, heroImage, cards } = data; return ( -
      -
      -

      - {title} -

      - -
      -

      + +

      +
      +

      - {taglineLabel} -

      -

      - {taglineBody} -

      -

      -
      + {title} +

      - {heroImage?.__image_url__ && ( - {heroImage.__image_prompt__} - )} - -
      - {cards?.map((card, index) => ( -
      +

      - {card.heading} + {taglineLabel}

      - {card.body} + {taglineBody}

      - ))} +
      + + {heroImage?.__image_url__ && ( + {heroImage.__image_prompt__} + )} + +
      + {cards?.map((card, index) => ( +
      +

      + {card.heading} +

      +

      + {card.body} +

      +
      + ))} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx index a3243257..df91222f 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx @@ -51,44 +51,47 @@ const BusinessChallengesGridSlide = ({ data }: { data: Partial }) => const { title, challenges } = data; return ( -
      -
      -

      - {title} -

      -
      - + <> +
      - {challenges?.map((challenge, index) => ( -
      -

      - {challenge.heading} -

      -

      - {challenge.body} -

      -
      - ))} +
      +

      + {title} +

      +
      + +
      + {challenges?.map((challenge, index) => ( +
      +

      + {challenge.heading} +

      +

      + {challenge.body} +

      +
      + ))} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx index 2987d3e0..a221de3e 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -117,103 +117,106 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { const { title, subtitle, columns, rows, checkIcon, crossIcon } = data; return ( -
      -
      -

      - {title} -

      -

      - {subtitle} -

      -
      - -
      -
      -
      - {columns?.map((column, index) => ( -
      - {column} -
      - ))} + <> + +
      +
      +

      + {title} +

      +

      + {subtitle} +

      - {rows?.map((row, index) => { - const cells: ("check" | "cross" | "empty")[] = [ - row.cell1, - row.cell2, - row.cell3, - row.cell4, - ]; - - return ( -
      +
      +
      +
      + {columns?.map((column, index) => (
      - {row.label} + {column}
      + ))} +
      - {cells?.map((status, cellIndex) => ( + {rows?.map((row, index) => { + const cells: ("check" | "cross" | "empty")[] = [ + row.cell1, + row.cell2, + row.cell3, + row.cell4, + ]; + + return ( +
      - + {row.label}
      - ))} -
      - ); - })} + + {cells?.map((status, cellIndex) => ( +
      + +
      + ))} +
      + ); + })} +
      -
      + ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx index 0da1e3a1..062d44cf 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonTableWithTextSlide.tsx @@ -78,72 +78,46 @@ const ComparisonTableWithTextSlide = ({ data }: { data: Partial }) = const { title, subtitle, columns, highlightedHeaderIndex, rows } = data; return ( -
      -
      -

      - {title} -

      -

      - {subtitle} -

      -
      - + <> +
      -
{column} @@ -115,12 +136,23 @@ const ComparisonTableWithTextSlide = ({ data }: { data: Partial }) =
{cell}
+

+ {title} +

+

+ {subtitle} +

+ + +
-
- - {columns?.map((column, index) => { - const isHighlighted = index + 1 === highlightedHeaderIndex; - return ( - - ); - })} - - - - - {rows?.map((row, rowIndex) => { - const cells = [row.cell1, row.cell2, row.cell3, row.cell4]; - const isHighlighted = rowIndex + 1 === highlightedHeaderIndex; - - return ( - - {cells?.map((cell, cellIndex) => ( - + + {columns?.map((column, index) => { + const isHighlighted = index + 1 === highlightedHeaderIndex; + return ( + - ); - })} - -
- {column} -
+
}) = : "var(--primary-color,#123f38)", }} > - {cell} - - ))} -
+ {column} + + ); + })} + + + + + {rows?.map((row, rowIndex) => { + const cells = [row.cell1, row.cell2, row.cell3, row.cell4]; + const isHighlighted = rowIndex + 1 === highlightedHeaderIndex; + + return ( + + {cells?.map((cell, cellIndex) => ( + + {cell} + + ))} + + ); + })} + + +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx index 87557f15..22a02183 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx @@ -42,61 +42,64 @@ const CoverSlide = ({ data }: { data: Partial }) => { const { logoImage, label, titleLine1, titleLine2, backgroundImage } = data; return ( -
+ <> +
-
- - {logoImage?.__image_prompt__ - -

- {label} -

-
- -
-

- {titleLine1} -
- {titleLine2} -

-
-
- - {backgroundImage?.__image_url__ && ( - {backgroundImage.__image_prompt__ - )} - -
-
+ > +
+
+ + {logoImage?.__image_prompt__ + +

+ {label} +

+
+ +
+

+ {titleLine1} +
+ {titleLine2} +

+
+
+ + {backgroundImage?.__image_url__ && ( + {backgroundImage.__image_prompt__ + )} + +
+
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx index f11e7ba5..41c854b6 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx @@ -85,62 +85,65 @@ const ImageGallerySlide = ({ data }: { data: Partial }) => { } = data; return ( -
-
+ <> + +
+
-
-

- {title} -

-

- {description} -

+
+

+ {title} +

+

+ {description} +

+
+
+ {topCenterImage?.__image_prompt__ + {topRightImage?.__image_prompt__ +
-
+ + +
{topCenterImage?.__image_prompt__ {topRightImage?.__image_prompt__ + {bottomRightImage?.__image_prompt__}
- - -
- {bottomWideImage?.__image_prompt__} - {bottomCenterImage?.__image_prompt__} - {bottomRightImage?.__image_prompt__} -
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx index 848b7ac6..7eeae599 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/IntroductionSlide.tsx @@ -55,57 +55,60 @@ const IntroductionSlide = ({ data }: { data: Partial }) => { const { title, portraitImage, blocks } = data; return ( -
-
-
- - {portraitImage?.__image_url__ && ( - {portraitImage.__image_prompt__} - )} -
- -
-

+ +
+
+
- {title} -

-
- {blocks?.map((block, index) => ( -
-

- {block.label} -

-

- {block.body} -

-
- ))} + {portraitImage?.__image_url__ && ( + {portraitImage.__image_prompt__} + )} +
+ +
+

+ {title} +

+ +
+ {blocks?.map((block, index) => ( +
+

+ {block.label} +

+

+ {block.body} +

+
+ ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx index 2c5d5ff0..d81f16bb 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx @@ -62,72 +62,75 @@ const KpiCardsSlide = ({ data }: { data: Partial }) => { const { title, kpiIcon, backgroundImage, items } = data; return ( -
- {backgroundImage?.__image_url__ && ( - {backgroundImage?.__image_prompt__} - )} - + <> +
+ > + {backgroundImage?.__image_url__ && ( + {backgroundImage?.__image_prompt__} + )} -
-

- {title} -

-
+
-
- {items?.map((item, index) => ( -
+

+ {title} +

+
+ +
+ {items?.map((item, index) => (
- {kpiIcon?.__icon_query__} +
+ {kpiIcon?.__icon_query__} +
+

+ {item.value} +

+

+ {item.body} +

-

- {item.value} -

-

- {item.body} -

-
- ))} + ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx index 07f887f2..03738a63 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MarketOpportunitySlide.tsx @@ -56,76 +56,79 @@ const MarketOpportunitySlide = ({ data }: { data: Partial }) => { const { title, subtitle, bullets, values } = data; return ( -
-
-

- {title} -

-

- {subtitle} -

-
- -
- {bullets?.map((bullet, index) => ( -
- -

- {bullet.text} -

- - -
- ))} -
- -
- {values?.map((value, index) => ( -
+ +
+
+

-

+

+ {subtitle} +

+

+ +
+ {bullets?.map((bullet, index) => ( +
+ +

+ {bullet.text} +

+ + +
+ ))} +
+ +
+ {values?.map((value, index) => ( +
- {value} -

-
- ))} +

+ {value} +

+
+ ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx index 7094003a..7921e215 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MeetTeamSlide.tsx @@ -92,78 +92,81 @@ const MeetTeamSlide = ({ data }: { data: Partial }) => { const { title, taglineLabel, taglineBody, members } = data; return ( -
-
-

- {title} -

- -
-

+ +

+
+

- {taglineLabel} -

-

- {taglineBody} -

+ {title} +

+ +
+

+ {taglineLabel} +

+

+ {taglineBody} +

+
+
+ +
+ {members?.map((member, index) => ( +
+ {member.image.__image_prompt__} +
+

+ {member.title} +

+

+ {member.name} +

+
+
+ ))}
- -
- {members?.map((member, index) => ( -
- {member.image.__image_prompt__} -
-

- {member.title} -

-

- {member.name} -

-
-
- ))} -
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx index ea7fd434..bf81b548 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/MissionVisionSlide.tsx @@ -44,73 +44,76 @@ const MissionVisionSlide = ({ data }: { data: Partial }) => { const { title, missionLabel, missionBody, visionLabel, visionBody, image } = data; return ( -
-
-
-

- {title} -

-
+ <> + +
+
+
+

+ {title} +

+
-
-

- {missionLabel} -

-

- {missionBody} -

-
+

+ {missionLabel} +

+

+ {missionBody} +

+
-
-

- {visionLabel} -

-

+ {visionLabel} +

+

+ {visionBody} +

+
+
- {visionBody} -

-
-
- {image?.__image_url__ && ( - {image.__image_prompt__} - )} + {image?.__image_url__ && ( + {image.__image_prompt__} + )} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx index 1d59b2f0..25d76baf 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx @@ -61,90 +61,93 @@ const OurServicesSlide = ({ data }: { data: Partial }) => { const { title, taglineLabel, taglineBody, featureImage, services } = data; return ( -
-
-
+ <> + +
+
+
-

- {title} -

- -
-

- {taglineLabel} -

-

- {taglineBody} -

+ {title} +

+ +
+

+ {taglineLabel} +

+

+ {taglineBody} +

+
-
-
- - {featureImage?.__image_url__ && ( - {featureImage?.__image_prompt__} - )} -
-
- - - -
- {services?.map((service, index) => (
-

- {service.heading} -

-

- {service.body} -

+ + {featureImage?.__image_url__ && ( + {featureImage?.__image_prompt__} + )}
- ))} +
+ + + +
+ {services?.map((service, index) => ( +
+

+ {service.heading} +

+

+ {service.body} +

+
+ ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx index e9a4a4dd..9bcc9f87 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx @@ -88,83 +88,86 @@ const PricingPlanSlide = ({ data }: { data: Partial }) => { const { title, featureIcon, plans } = data; return ( -
-
-

- {title} -

-
+ <> + +
+
+

+ {title} +

+
-
- {plans?.map((plan, index) => { - const active = plan.highlighted; - return ( -
-

+ {plans?.map((plan, index) => { + const active = plan.highlighted; + return ( +

- {plan.price} -

-

- {plan.description} -

+

+ {plan.price} +

+

+ {plan.description} +

-
- {plan.features.map((feature, featureIndex) => ( -
- {featureIcon?.__icon_query__} -

- {feature} -

-
- ))} +
+ {plan.features.map((feature, featureIndex) => ( +
+ {featureIcon?.__icon_query__} +

+ {feature} +

+
+ ))} +
-
- ); - })} + ); + })} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx index 5b1cae5f..21661a42 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx @@ -78,108 +78,111 @@ const ProcessSlide = ({ data }: { data: Partial }) => { const { title, steps } = data; return ( -
-
-

- {title} -

+ <> + +
+
+

+ {title} +

+
+ + + +
+ {steps?.map((step, index) => { + if (index % 2 === 0) { + return ( +
+
+
+ {step.icon.__icon_query__} + + + +
+
+ + + +
+
+
+

+ {step.label} +

+

+ {step.body} +

+
+
+ ) + } + else { + return ( +
+
+

+ {step.label} +

+

+ {step.body} +

+
+ +
+
+ + + + {step.icon.__icon_query__} +
+
+ + + +
+
+
+ ) + } + })} +
- - - -
- {steps?.map((step, index) => { - if (index % 2 === 0) { - return ( -
-
-
- {step.icon.__icon_query__} - - - -
-
- - - -
-
-
-

- {step.label} -

-

- {step.body} -

-
-
- ) - } - else { - return ( -
-
-

- {step.label} -

-

- {step.body} -

-
- -
-
- - - - {step.icon.__icon_query__} -
-
- - - -
-
-
- ) - } - })} -
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx index 3b564c5b..cc33eaf3 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx @@ -332,376 +332,379 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { const donutTotal = (donutData ?? []).reduce((sum, item) => sum + item.value, 0) || 1; return ( -
- {sideImage?.__image_url__ && ( - {sideImage.__image_prompt__} - )} - + <> +
-

- {title} -

- -
-

- {taglineLabel} -

-

- {taglineBody} -

-
-
- -
-

- {chartTitle} -

- - {activeChartStyle === "mini-bars" && ( - <> -
- - - - - `$${value}`} - axisLine={false} - tickLine={false} - tick={{ fill: "var(--background-text,#6C7271)", fontSize: 10 }} - /> - - - - -
- -
-

- {footerLabel} -

-

- {footerValue} -

-
- + {sideImage?.__image_url__ && ( + {sideImage.__image_prompt__} )} - {activeChartStyle === "donut" && ( -
-
- - - - {(donutData ?? []).map((entry, index) => ( - - ))} - - - -
+
+

+ {title} +

-
- {(donutData ?? []).map((entry, index) => { - const percent = Math.round((entry.value / donutTotal) * 100); - return ( -
-
- +
+

+ {taglineLabel} +

+

+ {taglineBody} +

+
+
+ +
+

+ {chartTitle} +

+ + {activeChartStyle === "mini-bars" && ( + <> +
+ + + + + `$${value}`} + axisLine={false} + tickLine={false} + tick={{ fill: "var(--background-text,#6C7271)", fontSize: 10 }} + /> + + + + +
+ +
+

+ {footerLabel} +

+

+ {footerValue} +

+
+ + )} + + {activeChartStyle === "donut" && ( +
+
+ + + + {(donutData ?? []).map((entry, index) => ( + + ))} + + + +
+ +
+ {(donutData ?? []).map((entry, index) => { + const percent = Math.round((entry.value / donutTotal) * 100); + return ( +
+
+ +

+ {legendLabels?.[index] ?? entry.name} +

+

- {legendLabels?.[index] ?? entry.name} + {percent}%

-

- {percent}% -

-
- ); - })} + ); + })} +
-
- )} + )} - {activeChartStyle === "grouped-bars" && ( -
-
- - - - - - +
+ + - + + + - - - + + + + + + + +
+ +
+
+ +

+ {legendLabels?.[0] ?? "Option A"} +

+
+
+ +

+ {legendLabels?.[1] ?? "Option B"} +

+
+
+
+ )} + + {activeChartStyle === "dual-line" && ( +
+
+ + + + - - - -
+ -
- -

- {legendLabels?.[0] ?? "Option A"} -

-
-
- -

- {legendLabels?.[1] ?? "Option B"} -

-
-
-
- )} - - {activeChartStyle === "dual-line" && ( -
-
- - - - - - - - - -
- -
-
- -

- {legendLabels?.[0] ?? "Option A"} -

-
-
- -

- {legendLabels?.[1] ?? "Option B"} -

-
-
-
- )} -
- -
-
- {visibleMetricCards.map((metric, index) => ( -
-
-
- {usePulseFallback ? ( - - ) : ( - {metricIcon?.__icon_query__} - )} + + + + +
+ +
+
+ +

+ {legendLabels?.[0] ?? "Option A"} +

+
+
+ +

+ {legendLabels?.[1] ?? "Option B"} +

+
+
+
+ )} +
+ +
+
+ {visibleMetricCards.map((metric, index) => ( +
+
+
+ {usePulseFallback ? ( + + ) : ( + {metricIcon?.__icon_query__} + )} +
+

+ {metric.value} +

- {metric.value} + {metric.body}

-

- {metric.body} -

-
- ))} + ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx index deb4f2ec..c028d3ab 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx @@ -53,60 +53,63 @@ const TableOfContentSlide = ({ data }: { data: Partial }) => { const { title, description, sections } = data; return ( -
-
-
-
- {sections?.map((section, index) => ( -
-
+ <> + +
+
+
+
+ {sections?.map((section, index) => ( +
+
+

+ {section.title} +

+

+ {section.description} +

+

- {section.title} -

-

- {section.description} + {section.number}

-

- {section.number} -

-
- ))} + ))} +
+
+ +
+

+ {title} +

+

+ {description} +

- -
-

- {title} -

-

- {description} -

-
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx index cd98a65c..790f3ac0 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx @@ -10,7 +10,7 @@ const InsightItemSchema = z.object({ title: z.string().min(3).max(12).meta({ description: "Short insight title shown next to the icon.", }), - description: z.string().min(20).max(84).meta({ + description: z.string().min(20).max(40).meta({ description: "Supporting text shown below the insight title.", }), }); @@ -40,7 +40,7 @@ export const Schema = z.object({ }), items: z .array(InsightItemSchema) - .min(3) + .max(3) .default([ { title: "Title 1", description: "Ut enim ad minima veniam, quis." }, @@ -48,10 +48,10 @@ export const Schema = z.object({ { title: "Title 2", description: "Ut enim ad minima veniam, quis." }, ]) .meta({ - description: "Three analysis points shown in the left column.", + description: "Three analysis points shown in the left column,maximum 3 items", }), chartData: flexibleChartDataSchema.default({ - type: "pie", + type: "bar", data: [ { name: "Mon", value: 120 }, { name: "Tue", value: 200 }, @@ -81,7 +81,7 @@ const DataAnalysisBarSlide = ({ data }: { data: Partial }) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
; -const getChartHeight = (count: number, hasMetrics: boolean) => { - if (count <= 2) return hasMetrics ? 230 : 280; - if (count <= 3) return hasMetrics ? 210 : 260; - return hasMetrics ? 160 : 180; -}; - - function SummaryCard({ value, label, @@ -187,7 +180,7 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial }) => className="relative flex flex-col h-[720px] w-[1280px] overflow-hidden bg-[#F9F8F8]" style={{ backgroundColor: "var(--background-color,#F9F8F8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) => {halfChart && halfChart.length > 0 &&
@@ -245,9 +238,9 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial }) =>
} {otherHalfChart && otherHalfChart.length > 0 &&
@@ -255,9 +248,7 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial }) =>
-
diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx index 0442e53a..3481d01a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx @@ -41,13 +41,14 @@ export const Schema = z.object({ description: "Featured insight paragraph shown in the left content area.", }), chartData: flexibleChartDataSchema.default({ - type: "pie", + type: "line-dual", data: [ { name: "Q1", value: 45 }, { name: "Q2", value: 72 }, { name: "Q3", value: 58 }, { name: "Q4", value: 89 }, ], + }), legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({ description: "Legend label shown below the chart.", @@ -70,7 +71,7 @@ const DataAnalysisInsightBarSlide = ({ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
; type StatMetric = { value: string; - label: string; - description: string; + label?: string; + description?: string; }; function StatPill({ metrics }: { metrics: StatMetric[] }) { @@ -96,13 +95,13 @@ function StatPill({ metrics }: { metrics: StatMetric[] }) { }} > {metrics.map((metric, index) => ( - +
-

{metric.value}

-

{metric.label}

-

+

{metric.value}

+ {metric.label &&

{metric.label}

} + {metric.description &&

{metric.description} -

+

}
{index === 0 && (
@@ -117,7 +116,7 @@ function StatPill({ metrics }: { metrics: StatMetric[] }) {
)} - +
))}
); @@ -134,7 +133,7 @@ const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) =>
-
@@ -165,7 +164,7 @@ const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => {seriesBLabel} -
+
}
diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx index 26260404..b94037ee 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx @@ -56,7 +56,7 @@ const DataAnalysisListSlide = ({ data }: { data: Partial }) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) => { className='relative w-[1280px] h-[720px] aspect-video flex flex-col justify-center items-center bg-white' style={{ backgroundColor: "var(--background-color,#ffffff)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} > diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx index 16eb8267..58f572fa 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx @@ -49,7 +49,7 @@ const IntroductionImageSlide = ({ data }: { data: Partial }) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) => { {body}

-
    {bullets?.map((bullet, index) => ( -
  • - {bullet} -
  • +
    +

    + {bullet} +

    +
    ))} -
+
diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx index 8acd65a6..eed933ac 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx @@ -5,10 +5,10 @@ const MetricSchema = z.object({ value: z.string().min(1).max(6).meta({ description: "Primary metric value shown in the card.", }), - label: z.string().min(3).max(10).meta({ + label: z.string().min(3).max(10).optional().meta({ description: "Short metric label shown below the value.", }), - description: z.string().min(6).max(20).meta({ + description: z.string().min(6).max(20).optional().meta({ description: "Supporting text shown below the label.", }), }); @@ -73,8 +73,8 @@ export type SchemaType = z.infer; type StatMetric = { value: string; - label: string; - description: string; + label?: string; + description?: string; }; function StatPill({ @@ -101,13 +101,13 @@ function StatPill({ key={`${metric.value}-${metric.label}-${index}`} className={``} > -

+

{metric.value}

-

{metric.label}

-

+ {metric.label &&

{metric.label}

} + {metric.description &&

{metric.description} -

+

}
{index === 0 &&
@@ -139,7 +139,7 @@ const IntroductionStatsSlide = ({ data }: { data: Partial }) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) => { {body}

-
    {bullets?.map((bullet, index) => ( -
  • - {bullet} -
  • +
    +

    + {bullet} +

    +
    ))} -
+
diff --git a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx index 530a0391..6bf25f5a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx @@ -70,7 +70,7 @@ const MilestoneSlide = ({ data }: { data: Partial }) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#F9F8F8]" style={{ backgroundColor: "var(--background-color,#F9F8F8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
; type StatMetric = { value: string; - label: string; - description: string; + label?: string; + description?: string; }; function StatPill({ @@ -94,13 +94,13 @@ function StatPill({ key={`${metric.value}-${metric.label}-${index}`} className={``} > -

+

{metric.value}

-

{metric.label}

-

+ {metric.label &&

{metric.label}

} + {metric.description &&

{metric.description} -

+

}
{index === 0 &&
@@ -131,7 +131,7 @@ const PerformanceSnapshotSlide = ({ data }: { data: Partial }) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" style={{ backgroundColor: "var(--background-color,#f9f8f8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
{ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#F9F8F8]" style={{ backgroundColor: "var(--background-color,#F9F8F8)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
}) => { className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-white" style={{ backgroundColor: "var(--background-color,#ffffff)", - fontFamily: "var(--body-font-family,Nunito Sans)", + fontFamily: "var(--body-font-family,Helvetica Neue)", }} >
Date: Thu, 9 Apr 2026 15:08:01 +0545 Subject: [PATCH 08/18] feat: New template improvements --- .../ProductOverview/BusinessChallengesGridSlide.tsx | 4 ++-- .../ProductOverview/ComparisonChartSlide.tsx | 6 +++--- .../ProductOverview/ImageGallerySlide.tsx | 6 +++--- .../ProductOverview/KpiCardsSlide.tsx | 6 +++--- .../ProductOverview/PricingPlanSlide.tsx | 4 ++-- .../ProductOverview/ReportSnapshotSlide.tsx | 8 ++++---- .../ProductOverview/TableOfContentSlide.tsx | 8 ++++---- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx index df91222f..1c337423 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx @@ -54,7 +54,7 @@ const BusinessChallengesGridSlide = ({ data }: { data: Partial }) => <>
}) =>
{challenges?.map((challenge, index) => ( diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx index a221de3e..a2ac164a 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -150,7 +150,7 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { {columns?.map((column, index) => (
}) => { style={{ borderColor: "var(--stroke,#c5cccb)" }} >
}) => { {cells?.map((status, cellIndex) => (
}) => { {title}

{description} @@ -134,12 +134,12 @@ const ImageGallerySlide = ({ data }: { data: Partial }) => { {bottomCenterImage?.__image_prompt__} {bottomRightImage?.__image_prompt__}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx index d81f16bb..19a9235b 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/KpiCardsSlide.tsx @@ -8,10 +8,10 @@ export const slideLayoutDescription = const KpiSchema = z.object({ value: z.string().max(5).meta({ - description: "Primary KPI value shown in a card.", + description: "Primary KPI value shown in a card. Should be less than 5 characters.", }), - body: z.string().max(20).meta({ - description: "Short KPI supporting text.", + body: z.string().max(16).meta({ + description: "Short KPI supporting text. Should be less than 16 characters.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx index 9bcc9f87..95b10c34 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx @@ -10,11 +10,11 @@ const PlanSchema = z.object({ price: z.string().min(4).max(12).meta({ description: "Plan price label shown at the top of each card.", }), - description: z.string().max(20).meta({ + description: z.string().max(18).meta({ description: "Short statement describing the plan.", }), features: z - .array(z.string().max(18)) + .array(z.string().max(14)) .max(4) .meta({ diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx index cc33eaf3..d158a8e6 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx @@ -365,7 +365,7 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { {taglineLabel}

{taglineBody} @@ -374,7 +374,7 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => {

@@ -660,10 +660,10 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => {
-
+
{visibleMetricCards.map((metric, index) => (
}) => { >
-
+
{sections?.map((section, index) => (
@@ -75,12 +75,12 @@ const TableOfContentSlide = ({ data }: { data: Partial }) => { > {section.title}

-

{section.description} -

+

}

Date: Thu, 9 Apr 2026 17:30:21 +0545 Subject: [PATCH 09/18] refactor: better 'Code' template schema and description --- .../Code/CodeSlide01RoadmapCover.tsx | 16 +++++----- .../Code/CodeSlide02CodeExplanationSplit.tsx | 28 +++++++++------- .../Code/CodeSlide03ApiRequestResponse.tsx | 6 ++-- .../Code/CodeSlide04FeatureGrid.tsx | 16 +++++----- .../Code/CodeSlide05ComparisonTable.tsx | 10 +++--- .../Code/CodeSlide06Workflow.tsx | 12 +++---- .../Code/CodeSlide07UseCaseList.tsx | 12 +++---- .../Code/CodeSlide08CodeExplanationText.tsx | 16 +++++----- .../Code/CodeSlide09TableOfContent.tsx | 26 +++++++-------- .../Code/CodeSlide10MetricsSplit.tsx | 28 ++++++++-------- .../Code/CodeSlide11MetricsGrid.tsx | 32 +++++++++---------- 11 files changed, 105 insertions(+), 97 deletions(-) diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx index 4e100bca..36de395f 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx @@ -1,16 +1,16 @@ import * as z from "zod"; -export const slideLayoutId = "code-roadmap-cover-slide"; -export const slideLayoutName = "Code Roadmap Cover Slide"; +export const slideLayoutId = "cover-slide"; +export const slideLayoutName = "Cover Slide"; export const slideLayoutDescription = - "A centered opening slide with company name, roadmap title, and supporting subtitle."; + "Opening/cover/intro slide with organization/institution/presenter, presentation title/heading , and supporting subtitle."; export const Schema = z.object({ - companyName: z.string().min(2).max(18).default("COMPANY NAME").meta({ - description: "Organization name shown above the slide title.", + companyName: z.string().min(2).max(18).optional().default("COMPANY NAME").meta({ + description: "Optional organization/institution/presenter name shown above the slide title.", }), title: z.string().min(8).max(28).default("Development Roadmap").meta({ - description: "Primary slide heading.", + description: "Title/heading of the slide.", }), subtitle: z .string() @@ -22,8 +22,8 @@ export const Schema = z.object({ .meta({ description: "Supporting subtitle shown under the heading.", }), - pageLabel: z.string().min(3).max(8).default("1 / 11").meta({ - description: "Bottom pagination label.", + pageLabel: z.string().min(3).max(8).optional().default("1 / 11").meta({ + description: "", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx index cef31779..9daa5dac 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx @@ -4,16 +4,22 @@ import * as z from "zod"; export const slideLayoutId = "code-explanation-split-slide"; export const slideLayoutName = "Code Explanation Split Slide"; export const slideLayoutDescription = - "A two-column slide with a code panel on the left and descriptive explanation on the right."; + "A two-column slide with a code panel on the left and description on the right."; export const Schema = z.object({ title: z.string().min(8).max(24).default("Code + Explanation").meta({ description: "Slide heading shown at the top-left.", }), codeSnippet: z.object({ - language: z.string().min(2).max(10), - fileName: z.string().min(3).max(30), - content: z.string().min(20).max(520), + language: z.string().min(2).max(10).meta({ + description: "Programming language of the snippet", + }), + fileName: z.string().min(3).max(30).meta({ + description: "File name label shown above the code snippet.", + }), + content: z.string().min(20).max(520).meta({ + description: "The actual code content to be displayed.", + }), }).default({ language: "tsx", fileName: "components/UserAuth.tsx", @@ -37,10 +43,10 @@ export function UserAuth() { }).meta({ description: "Code sample shown in the left panel.", }), - explanationTitle: z.string().min(4).max(20).default("Explanation").meta({ - description: "Heading shown above the explanatory paragraph.", + descriptionTitle: z.string().min(4).max(20).default("Description").meta({ + description: "Heading shown above the paragraph.", }), - explanation: z + description: z .string() .min(40) .max(360) @@ -48,9 +54,9 @@ export function UserAuth() { "This component manages credentials as local state and submits them through an async handler. The login utility abstracts network details while the handler keeps the UI flow predictable. Keep validation and side effects isolated so changes remain safe when authentication requirements evolve. " ) .meta({ - description: "Explanation paragraph shown in the right column.", + description: "Description paragraph shown in the right column.", }), - pageLabel: z.string().min(3).max(8).default("2 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("2 / 11").meta({ description: "Bottom pagination label.", }), }); @@ -104,9 +110,9 @@ const CodeSlide02CodeExplanationSplit = ({

-

{data.explanationTitle}

+

{data.descriptionTitle}

- {data.explanation} + {data.description}

diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx index 784b30a7..e48ebf57 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx @@ -1,8 +1,8 @@ import * as z from "zod"; -export const slideLayoutId = "code-api-request-response-slide"; -export const slideLayoutName = "Code API Request Response Slide"; +export const slideLayoutId = "api-request-response-slide"; +export const slideLayoutName = "API Request Response Slide"; export const slideLayoutDescription = "An API-focused slide with endpoint metadata, request payload, and response payload."; @@ -59,7 +59,7 @@ export const Schema = z.object({ }).meta({ description: "Response payload example.", }), - pageLabel: z.string().min(3).max(8).default("3 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("3 / 11").meta({ description: "Bottom pagination label.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx index 6416862c..8a87b8a7 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx @@ -2,10 +2,10 @@ import * as z from "zod"; const FeatureCardSchema = z.object({ title: z.string().min(3).max(17).meta({ - description: "Feature title shown on each card.", + description: "Title shown on each card.", }), description: z.string().min(18).max(80).meta({ - description: "Supporting feature description.", + description: "Description shown on each card.", }), icon: z.object({ __icon_url__: z.string().meta({ @@ -18,14 +18,14 @@ const FeatureCardSchema = z.object({ __icon_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/icons/placeholder.svg", __icon_query__: "check icon", }).meta({ - description: "Icon used for each feature bullet in plan cards.", + description: "Suiting icon used for each bullet in plan cards.", }), }); -export const slideLayoutId = "code-feature-grid-slide"; -export const slideLayoutName = "Code Feature Grid Slide"; +export const slideLayoutId = "cards-grid-slide"; +export const slideLayoutName = "Cards Grid Slide"; export const slideLayoutDescription = - "A six-card feature summary grid with icon badges and compact descriptions."; + "A list of cards in grid with title, icon and compact description in each."; export const Schema = z.object({ title: z.string().min(6).max(20).default("Feature Grid").meta({ @@ -33,7 +33,7 @@ export const Schema = z.object({ }), features: z .array(FeatureCardSchema) - .min(3) + .min(1) .max(6) .default([ { @@ -88,7 +88,7 @@ export const Schema = z.object({ .meta({ description: "Six feature cards displayed in a 3x2 grid.", }), - pageLabel: z.string().min(3).max(8).default("4 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("4 / 11").meta({ description: "Bottom pagination label.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx index 587c2615..1f55acec 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx @@ -15,10 +15,10 @@ const ComparisonRowSchema = z.object({ }), }); -export const slideLayoutId = "code-comparison-table-slide"; -export const slideLayoutName = "Code Comparison Table Slide"; +export const slideLayoutId = "table-slide"; +export const slideLayoutName = "Table Slide"; export const slideLayoutDescription = - "A framework comparison table with feature rows and highlighted compatibility marks."; + "A slide with title and a table."; export const Schema = z.object({ title: z.string().min(6).max(18).default("Comparison").meta({ @@ -29,7 +29,7 @@ export const Schema = z.object({ }).default(["Feature", "Column 1", "Column 2", "Column 3"]), rows: z .array(ComparisonRowSchema) - .min(6) + .min(1) .max(6) .default([ { feature: "Component-based", column1: "check", column2: "check", column3: "check" }, @@ -42,7 +42,7 @@ export const Schema = z.object({ .meta({ description: "Six comparison rows shown in the table.", }), - pageLabel: z.string().min(3).max(8).default("5 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("5 / 11").meta({ description: "Bottom pagination label.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx index 765de291..d64d4071 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx @@ -21,10 +21,10 @@ const WorkflowStepSchema = z.object({ }), }); -export const slideLayoutId = "code-workflow-slide"; -export const slideLayoutName = "Code Workflow Slide"; +export const slideLayoutId = "workflow-slide"; +export const slideLayoutName = "Workflow Slide"; export const slideLayoutDescription = - "A four-step workflow slide with cards and directional arrows between steps."; + "A workflow slide with cards and directional arrows between steps."; export const Schema = z.object({ title: z.string().min(6).max(16).default("Workflow").meta({ @@ -32,7 +32,7 @@ export const Schema = z.object({ }), steps: z .array(WorkflowStepSchema) - .min(4) + .min(1) .max(4) .default([ { @@ -69,9 +69,9 @@ export const Schema = z.object({ }, ]) .meta({ - description: "Four workflow steps shown in sequence.", + description: "Workflow steps shown in sequence.", }), - pageLabel: z.string().min(3).max(8).default("6 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("6 / 11").meta({ description: "Bottom pagination label.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx index 5defbc5d..d08f432f 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx @@ -1,17 +1,17 @@ import * as z from "zod"; -export const slideLayoutId = "code-use-case-list-slide"; -export const slideLayoutName = "Code Use Case List Slide"; +export const slideLayoutId = "bullet-list-slide"; +export const slideLayoutName = "Two Column Bullet List Slide"; export const slideLayoutDescription = - "A two-column numbered use-case list with eight compact items."; + "A two-column numbered string list with items."; export const Schema = z.object({ title: z.string().min(6).max(30).default("Usecase").meta({ description: "Slide title shown above the numbered list.", }), items: z - .array(z.string().min(4).max(8)) - .min(4) + .array(z.string().min(1).max(200)) + .min(1) .max(8) .default([ "Use pre-built component library for UI consistency", @@ -26,7 +26,7 @@ export const Schema = z.object({ .meta({ description: "Eight use-case items shown in two columns.", }), - pageLabel: z.string().min(3).max(8).default("7 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("7 / 11").meta({ description: "Bottom pagination label.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx index b4e3831c..0211fa9c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx @@ -1,18 +1,18 @@ import * as z from "zod"; -export const slideLayoutId = "code-explanation-text-slide"; -export const slideLayoutName = "Code Explanation Text Slide"; +export const slideLayoutId = "description-text-slide"; +export const slideLayoutName = "Description Text Slide"; export const slideLayoutDescription = - "A text-only explanation slide with generous whitespace for narrative documentation."; + "A text-only description slide tihe title/heading."; export const Schema = z.object({ title: z.string().min(8).max(30).default("Code + Explanation").meta({ description: "Main slide title shown at the top-left.", }), - explanationTitle: z.string().min(4).max(20).default("Explanation").meta({ + descriptionTitle: z.string().min(4).max(20).default("Explanation").meta({ description: "Subheading above the paragraph body.", }), - explanation: z + description: z .string() .max(360) @@ -22,7 +22,7 @@ export const Schema = z.object({ .meta({ description: "Long-form explanation body.", }), - pageLabel: z.string().min(3).max(8).default("8 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("8 / 11").meta({ description: "Bottom pagination label.", }), }); @@ -45,8 +45,8 @@ const CodeSlide08CodeExplanationText = ({ data }: { data: Partial })

{data.title}

-

{data.explanationTitle}

-

{data.explanation}

+

{data.descriptionTitle}

+

{data.description}

}) => { >

{metric.value}

{metric.label}

-

{metric.period}

+

{metric.subtext}

))}
diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx index ecce205a..3a2f9f51 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx @@ -4,18 +4,18 @@ const MetricSchema = z.object({ value: z.string().min(2).max(6).meta({ description: "Primary metric value.", }), - label: z.string().min(3).max(14).meta({ + label: z.string().min(3).max(15).meta({ description: "Metric label text.", }), - period: z.string().min(3).max(16).meta({ - description: "Metric period text.", + subtext: z.string().min(3).max(30).meta({ + description: "Metric subtext/description.", }), }); -export const slideLayoutId = "code-metrics-grid-slide"; -export const slideLayoutName = "Code Metrics Grid Slide"; +export const slideLayoutId = "metrics-grid-slide"; +export const slideLayoutName = "Metrics Grid Slide"; export const slideLayoutDescription = - "A six-card metrics grid for KPI overviews in code-focused decks."; + "A slide with metrics card grid and title at the top."; export const Schema = z.object({ title: z.string().min(6).max(18).default("Metrics").meta({ @@ -23,20 +23,20 @@ export const Schema = z.object({ }), metrics: z .array(MetricSchema) - .min(3) + .min(1) .max(6) .default([ - { value: "99.9%", label: "Uptime", period: "Last 12 months" }, - { value: "<100ms", label: "Response Time", period: "Last 12 months" }, - { value: "50k+", label: "Active Users", period: "Last 12 months" }, - { value: "99.9%", label: "Uptime", period: "Last 12 months" }, - { value: "<100ms", label: "Response Time", period: "Last 12 months" }, - { value: "50k+", label: "Active Users", period: "Last 12 months" }, + { value: "99.9%", label: "Uptime", subtext: "Last 12 months" }, + { value: "<100ms", label: "Response Time", subtext: "Last 12 months" }, + { value: "50k+", label: "Active Users", subtext: "Last 12 months" }, + { value: "99.9%", label: "Uptime", subtext: "Last 12 months" }, + { value: "<100ms", label: "Response Time", subtext: "Last 12 months" }, + { value: "50k+", label: "Active Users", subtext: "Last 12 months" }, ]) .meta({ - description: "Six KPI cards in a 3x2 grid.", + description: "Metrics cards in a grid.", }), - pageLabel: z.string().min(3).max(8).default("11 / 11").meta({ + pageLabel: z.string().min(3).max(8).optional().default("11 / 11").meta({ description: "Bottom pagination label.", }), }); @@ -74,7 +74,7 @@ const CodeSlide11MetricsGrid = ({ data }: { data: Partial }) => { >

{metric.value}

{metric.label}

-

{metric.period}

+

{metric.subtext}

))}
From e77b674bf1fb108e25c5183622f85ea83fb0824e Mon Sep 17 00:00:00 2001 From: Suraj Jha Date: Thu, 9 Apr 2026 21:46:31 +0545 Subject: [PATCH 10/18] refactor: 'Report' template schema correction --- .../Report/DataAnalysisBarSlide.tsx | 20 +++++------ .../Report/DataAnalysisInsightBarSlide.tsx | 12 +++---- .../Report/DataAnalysisLineStatsSlide.tsx | 14 ++++---- .../Report/DataAnalysisListSlide.tsx | 8 ++--- .../Report/IntroSlide.tsx | 26 +++++++++----- .../Report/IntroductionStatsSlide.tsx | 16 ++++----- .../Report/MilestoneSlide.tsx | 24 ++++++------- .../Report/PerformanceSnapshotSlide.tsx | 25 +++++-------- .../Report/ServicesSlide.tsx | 14 ++++---- .../Report/SolutionSlide.tsx | 34 +++++++++--------- .../Report/TeamSlide.tsx | 36 +++++++++---------- ...ide.tsx => TitleDescriptionImageSlide.tsx} | 15 ++++---- 12 files changed, 122 insertions(+), 122 deletions(-) rename electron/servers/nextjs/app/presentation-templates/Report/{IntroductionImageSlide.tsx => TitleDescriptionImageSlide.tsx} (90%) diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx index 790f3ac0..d4c5c643 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx @@ -7,21 +7,21 @@ import { ResponsiveContainer } from "recharts"; import { FlexibleReportChart, flexibleChartDataSchema } from "./flexibleReportChart"; const InsightItemSchema = z.object({ - title: z.string().min(3).max(12).meta({ - description: "Short insight title shown next to the icon.", + title: z.string().min(3).max(80).meta({ + description: "Bullet point title shown next to the icon.", }), - description: z.string().min(20).max(40).meta({ - description: "Supporting text shown below the insight title.", + description: z.string().min(20).max(120).meta({ + description: "Bullet point description shown below the title.", }), }); -export const slideLayoutId = "data-analysis-bar-slide"; -export const slideLayoutName = "Data Analysis Bar Slide"; +export const slideLayoutId = "bar-chart-with-bullet-list-title-description-icon-slide"; +export const slideLayoutName = "Bar Chart with Bullet List with Title Description Icon 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."; + "A slide with a title at the top, a vertical list of three bullet points with icon, title and description on the left, and a bar chart on the right."; export const Schema = z.object({ - title: z.string().min(3).max(28).default("Data Analysis").meta({ + title: z.string().min(3).max(80).default("Data Analysis").meta({ description: "Slide title shown at the top-left.", }), itemIcon: z @@ -40,7 +40,7 @@ export const Schema = z.object({ }), items: z .array(InsightItemSchema) - + .min(1) .max(3) .default([ { title: "Title 1", description: "Ut enim ad minima veniam, quis." }, @@ -63,7 +63,7 @@ export const Schema = z.object({ ], }), - legendLabel: z.string().min(3).max(32).default("Traditional Workflow").meta({ + legendLabel: z.string().min(3).max(50).default("Traditional Workflow").meta({ description: "Legend label shown below the chart.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx index 3481d01a..1f7aac3d 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx @@ -6,14 +6,14 @@ import { ResponsiveContainer } from "recharts"; import { FlexibleReportChart, flexibleChartDataSchema } from "./flexibleReportChart"; -export const slideLayoutId = "data-analysis-insight-bar-slide"; -export const slideLayoutName = "Data Analysis Insight Bar Slide"; +export const slideLayoutId = "title-description-chart-slide"; +export const slideLayoutName = "Title Description Chart 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 chart on the right with a legend below it."; + "A slide with a title at the top, description text in left and chart in the right."; export const Schema = z.object({ - title: z.string().min(3).max(12).default("Data Analysis").meta({ + title: z.string().min(3).max(80).default("Data Analysis").meta({ description: "Slide title shown at the top-left.", }), insightIcon: z @@ -32,13 +32,13 @@ export const Schema = z.object({ }), insightBody: z .string() - .min(80) + .min(30) .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.", + description: "Description text shown in the left content area.", }), chartData: flexibleChartDataSchema.default({ type: "line-dual", diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx index 5f16534c..05f0fb6f 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx @@ -19,18 +19,18 @@ const MetricSchema = z.object({ }); const StatColumnSchema = z.object({ - metrics: z.array(MetricSchema).min(2).max(2).meta({ + metrics: z.array(MetricSchema).min(0).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 slideLayoutId = "title-chart-metrics-cards-slide"; +export const slideLayoutName = "Title Chart with Metrics Cards 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."; + "A slide with a title at the top, chart in the left content area, and optional metric cards arranged side by side on the right."; export const Schema = z.object({ - title: z.string().min(3).max(12).default("Data Analysis").meta({ + title: z.string().min(3).max(80).default("Data Analysis").meta({ description: "Slide title shown at the top-left.", }), seriesALabel: z.string().min(3).max(20).default("Category A").meta({ @@ -56,7 +56,7 @@ export const Schema = z.object({ }), statColumns: z .array(StatColumnSchema) - .min(2) + .min(1) .max(2) .default([ { @@ -73,7 +73,7 @@ export const Schema = z.object({ }, ]) .meta({ - description: "Two stat cards shown on the right side of the slide.", + description: "Stat/metric cards shown on the right side of the slide.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx index b94037ee..8875d162 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx @@ -10,10 +10,10 @@ const AnalysisItemSchema = z.object({ }), }); -export const slideLayoutId = "data-analysis-list-slide"; -export const slideLayoutName = "Data Analysis List Slide"; +export const slideLayoutId = "bullet-list-with-icon-title-description-slide"; +export const slideLayoutName = "Bullet List with Icon Title Description 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."; + "A slide with a title at the top and a two-column list of bullets 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(12).default("Data Analysis").meta({ @@ -42,7 +42,7 @@ export const Schema = z.object({ { title: "Title 5", description: "Ut enim ad minima veniam, quis." }, ]) .meta({ - description: "Six analysis items distributed across two columns.", + description: "List of points contains a title and description.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx index e0925d55..bbd15bb7 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx @@ -1,18 +1,26 @@ import * as z from "zod"; export const Schema = z.object({ - title: z.string().min(1).max(12).default("Company's "), - subtitle: z.string().min(1).max(15).default("Report"), - name: z.string().min(1).max(10).default("John Doe"), - position: z.string().min(1).max(20).default("Company Name | Strategy, Content, growth"), + titleFirstLine: z.string().min(1).max(12).default("Company's ").meta({ + description: "First half of title or heading", + }), + titleSecondLine: z.string().min(1).max(12).default("Annual Report").meta({ + description: "Second half of title or heading", + }), + name: z.string().min(1).max(10).optional().default("John Doe").meta({ + description: "Name of the presenter/individual/company/organization.", + }), + position: z.string().min(1).max(20).default("Company Name | Strategy, Content, growth").meta({ + description: "Position or role of the presenter or address of the company/organization.", + }), }) export type SchemaType = z.infer; export const slideLayoutId = "intro-slide"; -export const slideLayoutName = "Intro Slide"; +export const slideLayoutName = "Intro/Cover 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."; + "A cover/intro slide with a 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 }) => { - const { title, subtitle, name, position } = data; + const { titleFirstLine, titleSecondLine, name, position } = data; return (
}) => { className="text-[#232223] text-[133px] italic text-center font-bold capitalize tracking-[-2.8px]" style={{ color: "var(--background-text,#232223)" }} > - {title} + {titleFirstLine}

- {subtitle} + {titleSecondLine}

}) => { className={`${isActive ? "text-white" : "text-[#157CFF]"} text-[42px] font-medium tracking-[0.18em]`} style={{ color: isActive ? "var(--primary-text,#ffffff)" : "var(--primary-color,#157CFF)" }} > - {item.stepNumber} + {item.bulletNumber}
@@ -130,8 +130,6 @@ const MilestoneSlide = ({ data }: { data: Partial }) => { ); })}
- -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx index e225a652..a1802442 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx @@ -5,10 +5,10 @@ const MetricSchema = z.object({ value: z.string().min(1).max(6).meta({ description: "Primary metric value shown in the pill.", }), - label: z.string().max(10).optional().meta({ + label: z.string().max(20).optional().meta({ description: "Short label shown below the metric value.", }), - description: z.string().min(6).max(20).optional().meta({ + description: z.string().min(6).max(50).optional().meta({ description: "Supporting metric description shown below the label.", }), }); @@ -19,18 +19,18 @@ const MetricColumnSchema = z.object({ }), }); -export const slideLayoutId = "performance-snapshot-slide"; -export const slideLayoutName = "Performance Snapshot Slide"; +export const slideLayoutId = "title-metrics-slide"; +export const slideLayoutName = "Title Metrics 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."; + "A slide with a title at the top and 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(12).default("Performance Snapshot").meta({ + title: z.string().min(3).max(80).default("Performance Snapshot").meta({ description: "Slide title shown at the top-left.", }), columns: z .array(MetricColumnSchema) - .min(2) + .min(1) .max(4) .default([ { @@ -50,12 +50,7 @@ export const Schema = z.object({ { 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.", @@ -77,8 +72,6 @@ function StatPill({ metrics: StatMetric[]; }) { - - return (
}) => {
-
+
{columns?.map((column, index) => ( {
{visibleCards?.map((card, index) => ( ))} @@ -141,8 +141,8 @@ const SolutionSlide = ({ data }: SolutionSlideProps) => {
{visibleCards?.map((card, index) => ( ))} diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx index 5fc59aac..0cee7914 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx @@ -2,11 +2,11 @@ import * as z from "zod"; const MemberSchema = z.object({ - title: z.string().min(2).max(12).meta({ - description: "Short role or title shown above the member name.", + subtext: z.string().min(2).max(40).meta({ + description: "Subtext for the image.", }), - name: z.string().min(2).max(20).meta({ - description: "Member name shown at the bottom of the card.", + title: z.string().min(2).max(40).meta({ + description: "Title/name/subject for the image", }), image: z.object({ __image_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg"), @@ -17,10 +17,10 @@ const MemberSchema = z.object({ }), }); -export const slideLayoutId = "team-slide"; -export const slideLayoutName = "Team Slide"; +export const slideLayoutId = "horizontal-height-spanning-images-with-title-slide"; +export const slideLayoutName = "Horizontal Height Spanning Images with Title 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."; + "A slide of 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 subtext line and a larger title line."; export const Schema = z.object({ members: z @@ -29,40 +29,40 @@ export const Schema = z.object({ .max(5) .default([ { - title: "Title", - name: "Lanny LA", + subtext: "Title", + title: "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", + subtext: "Title", + title: "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", + subtext: "Title", + title: "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", + subtext: "Title", + title: "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", + subtext: "Title", + title: "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", @@ -70,7 +70,7 @@ export const Schema = z.object({ }, ]) .meta({ - description: "Five team members rendered as portrait cards.", + description: "List of team members shown as portrait cards. Each member contains a title, subtext, and image.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx similarity index 90% rename from electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx index 58f572fa..23023436 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionImageSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx @@ -1,14 +1,14 @@ import * as z from "zod"; -export const slideLayoutId = "introduction-image-slide"; -export const slideLayoutName = "Introduction Image Slide"; +export const slideLayoutId = "title-description-image-slide"; +export const slideLayoutName = "Title Description 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."; + "A slide with a title at the top-left, a paragraph block beneath the title, a large supporting image anchored on the right side of the slide."; export const Schema = z.object({ title: z.string().min(3).max(12).default("Introduction").meta({ - description: "Slide title shown at the top-left.", + description: "Title/heading of the slide", }), body: z.string().max(250).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" @@ -16,9 +16,10 @@ export const Schema = z.object({ description: "Primary paragraph shown under the title.", }), bullets: z - .array(z.string().max(35)) - + .array(z.string().max(100)) + .min(0) .max(4) + .optional() .default([ "Ut enim ad minima veniam, quis nostrum", "Exercitationem ullam corporis suscipit", @@ -26,7 +27,7 @@ export const Schema = z.object({ "exercitationem ullam corporis suscipit", ]) .meta({ - description: "Bullet list shown in the lower-left area.", + description: "Optional bullet list shown after the description if required.", }), featureImage: z.object({ __image_url__: z.string(), From c957f4f63ae8a5c12c1d3dd3a05c12f24ac091aa Mon Sep 17 00:00:00 2001 From: Suraj Jha Date: Thu, 9 Apr 2026 21:47:07 +0545 Subject: [PATCH 11/18] refactor: report filename changed --- electron/servers/nextjs/app/presentation-templates/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index 9a40bcee..606af6cc 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -47,7 +47,7 @@ import TableOfContentSlide, { Schema as PoTableOfContentSchema, slideLayoutId as // 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 TitleDescriptionImageSlide, { Schema as RepIntroductionImageSchema, slideLayoutId as RepIntroductionImageId, slideLayoutName as RepIntroductionImageName, slideLayoutDescription as RepIntroductionImageDesc } from "./Report/TitleDescriptionImageSlide"; 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"; @@ -290,7 +290,7 @@ export const productOverviewTemplates: TemplateWithData[] = [ export const reportTemplates: TemplateWithData[] = [ createTemplateEntry(ReportIntroSlide, RepIntroSchema, RepIntroId, RepIntroName, RepIntroDesc, "report", "IntroSlide"), - createTemplateEntry(IntroductionImageSlide, RepIntroductionImageSchema, RepIntroductionImageId, RepIntroductionImageName, RepIntroductionImageDesc, "report", "IntroductionImageSlide"), + createTemplateEntry(TitleDescriptionImageSlide, RepIntroductionImageSchema, RepIntroductionImageId, RepIntroductionImageName, RepIntroductionImageDesc, "report", "TitleDescriptionImageSlide"), createTemplateEntry(IntroductionStatsSlide, RepIntroductionStatsSchema, RepIntroductionStatsId, RepIntroductionStatsName, RepIntroductionStatsDesc, "report", "IntroductionStatsSlide"), createTemplateEntry(SolutionSlide, RepSolutionSchema, RepSolutionId, RepSolutionName, RepSolutionDesc, "report", "SolutionSlide"), createTemplateEntry(MilestoneSlide, RepMilestoneSchema, RepMilestoneId, RepMilestoneName, RepMilestoneDesc, "report", "MilestoneSlide"), From 4d166be0c3532f7440c31baf12ccfa5a5d96c38e Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Thu, 9 Apr 2026 21:56:43 +0545 Subject: [PATCH 12/18] feat: New templates improvements --- electron/servers/nextjs/app/globals.css | 8 +- .../Education/EducationAboutSlide.tsx | 22 +- .../Education/EducationContentSplitSlide.tsx | 47 +- .../Education/EducationCoverSlide.tsx | 18 +- .../Education/EducationImageGallerySlide.tsx | 12 +- .../Education/EducationReportChartSlide.tsx | 10 +- .../Education/EducationServicesSplitSlide.tsx | 32 +- .../EducationStatisticsGridSlide.tsx | 57 +- .../EducationTableOfContentsSlide.tsx | 39 +- .../Education/EducationTimelineSlide.tsx | 51 +- .../BusinessChallengesCardsSlide.tsx | 22 +- .../BusinessChallengesGridSlide.tsx | 38 +- .../ProductOverview/ComparisonChartSlide.tsx | 163 +++-- .../ComparisonTableWithTextSlide.tsx | 142 ++-- .../ProductOverview/CoverSlide.tsx | 41 +- .../ProductOverview/ImageGallerySlide.tsx | 16 +- .../ProductOverview/IntroductionSlide.tsx | 22 +- .../ProductOverview/KpiCardsSlide.tsx | 24 +- .../MarketOpportunitySlide.tsx | 10 +- .../ProductOverview/MeetTeamSlide.tsx | 16 +- .../ProductOverview/MissionVisionSlide.tsx | 38 +- .../ProductOverview/OurServicesSlide.tsx | 50 +- .../ProductOverview/PricingPlanSlide.tsx | 22 +- .../ProductOverview/ProcessSlide.tsx | 56 +- .../ProductOverview/ReportSnapshotSlide.tsx | 681 +++++++++--------- .../ProductOverview/TableOfContentSlide.tsx | 34 +- 26 files changed, 881 insertions(+), 790 deletions(-) diff --git a/electron/servers/nextjs/app/globals.css b/electron/servers/nextjs/app/globals.css index 137382cc..aa9e7d95 100644 --- a/electron/servers/nextjs/app/globals.css +++ b/electron/servers/nextjs/app/globals.css @@ -84,7 +84,7 @@ strong { @font-face { font-family: 'Times New Roman'; - src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/times.ttf') format('truetype'); + src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/times.ttf') format('truetype'); font-style: normal; font-display: swap; @@ -92,7 +92,7 @@ strong { @font-face { font-family: 'Helvetica Neue'; - src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeueBold.otf') format('truetype'); + src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeue-Bold.otf') format('truetype'); font-weight: 600; font-style: normal; font-display: swap; @@ -100,7 +100,7 @@ strong { @font-face { font-family: 'Helvetica Neue'; - src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeueMedium.otf') format('truetype'); + src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeue-Medium.otf') format('truetype'); font-weight: 500; font-style: normal; font-display: swap; @@ -108,7 +108,7 @@ strong { @font-face { font-family: 'Helvetica Neue'; - src: url('https: //presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeueRoman.otf') format('truetype'); + src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeue-Roman.otf') format('truetype'); font-weight: 400; font-style: normal; font-display: swap; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx index 4bb11c5d..72402f2c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx @@ -1,22 +1,22 @@ import * as z from "zod"; -export const slideLayoutId = "education-about-slide"; -export const slideLayoutName = "Education About Slide"; +export const slideLayoutId = "about-slide"; +export const slideLayoutName = "About Slide"; export const slideLayoutDescription = - "A left text column with company introduction and a right-side visual grid made from one repeated image and tinted text panels."; + "A left text column with company/instructor/presenter/institute name and title introduction and a right-side visual grid made from one repeated image and tinted text panels."; export const Schema = z.object({ - companyName: z.string().min(3).max(22).default("Company Name").meta({ + name: z.string().min(3).max(22).default("Company Name").meta({ description: "Main heading in the left content column.", }), - intro: z.string().min(40).max(120).default( + intro: z.string().min(40).max(100).default( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et." ).meta({ description: "Bold intro text shown beneath the company heading.", }), body: z.string().min(120).max(280).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi." + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ).meta({ description: "Body paragraph in the left content section.", }), @@ -28,23 +28,23 @@ export const Schema = z.object({ }), topFeatureImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), - __image_prompt__: z.string().min(10).max(200).default("Office team collaboration"), + __image_prompt__: z.string().default("Office team collaboration"), }).default({ __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Office team collaboration", }).meta({ - description: "Single image reused in the right-side visual grid.", + description: "Single image reused in the top right-side visual grid.", }), bottomFeatureImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), - __image_prompt__: z.string().min(10).max(200).default("Office team collaboration"), + __image_prompt__: z.string().default("Office team collaboration"), }).default({ __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Office team collaboration", }).meta({ - description: "Single image reused in the right-side visual grid.", + description: "Single image reused in the bottom right-side visual grid.", }), }); @@ -66,7 +66,7 @@ const EducationAboutSlide = ({ data }: { data: Partial }) => {

- {data.companyName} + {data.name}

{data.intro} diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx index e12bb2a3..8be4223e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx @@ -1,39 +1,50 @@ import * as z from "zod"; -export const slideLayoutId = "education-content-split-slide"; -export const slideLayoutName = "Education Content Split Slide"; +export const slideLayoutId = "content-split-slide"; +export const slideLayoutName = "Content Split Slide"; export const slideLayoutDescription = "A left collage built from one repeated image and a right content block containing heading, tagline, and paragraph text."; export const Schema = z.object({ - heading: z.string().min(3).max(16).default("Heading").meta({ + heading: z.string().max(24).default("Heading").meta({ description: "Main right-side heading.", }), - tagline: z.string().min(3).max(12).default("TAGLINE").meta({ + tagline: z.string().max(12).default("TAGLINE").meta({ description: "Small uppercase label shown under the heading.", }), - body: z.string().min(80).max(300).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + body: z.string().max(300).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ).meta({ description: "Main descriptive paragraph on the right side.", }), - collageImage: z.object({ + images: z.array(z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80"), - __image_prompt__: z.string().min(10).max(200).default("Business team around a laptop"), - }).default({ + __image_prompt__: z.string().default("Business team around a laptop"), + })).min(1).max(3).default([{ __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Business team around a laptop", - }).meta({ - description: "Single image reused to create the left collage composition.", + }, + { + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Business team around a laptop", + }, + { + __image_url__: + "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", + __image_prompt__: "Business team around a laptop", + }, + ]).meta({ + description: "Array of images reused to create the left collage composition.", }), }); export type SchemaType = z.infer; const EducationContentSplitSlide = ({ data }: { data: Partial }) => { - const { heading, tagline, body, collageImage } = data; + const { heading, tagline, body, images } = data; return (

}) =>
{collageImage?.__image_prompt__}
{collageImage?.__image_prompt__}
{collageImage?.__image_prompt__}
diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx index d081716c..d8a1fa2a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx @@ -1,21 +1,21 @@ import * as z from "zod"; -export const slideLayoutId = "education-cover-slide"; -export const slideLayoutName = "Education Cover Slide"; +export const slideLayoutId = "cover-slide"; +export const slideLayoutName = "Cover Slide"; export const slideLayoutDescription = - "A full-bleed cover slide with a single background image, a strong violet overlay, and centered company/title text."; + "A full-bleed cover slide with a single background image, a strong violet overlay, and centered company/instructor/presenter/institute name and title text."; export const Schema = z.object({ - companyName: z.string().min(3).max(24).default("COMPANY NAME").meta({ - description: "Small uppercase company label shown above the main title.", + name: z.string().min(3).max(16).optional().default("COMPANY NAME").meta({ + description: "Company/instructor/presenter/institute name label shown above the main title.", }), title: z.string().min(6).max(32).default("PowerPoint Template").meta({ description: "Main centered title of the cover slide.", }), backgroundImage: z.object({ __image_url__: z.string().default("https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80"), - __image_prompt__: z.string().min(10).max(200).default("City business district buildings"), + __image_prompt__: z.string().default("City business district buildings"), }).default({ __image_url__: "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=80", @@ -28,7 +28,7 @@ export const Schema = z.object({ export type SchemaType = z.infer; const EducationCoverSlide = ({ data }: { data: Partial }) => { - const { companyName, title, backgroundImage } = data; + const { name, title, backgroundImage } = data; return (
}) => { className="relative z-10 flex h-full flex-col items-center justify-center text-center" style={{ color: "var(--primary-text,#ffffff)" }} > -

{companyName}

-

+ {name &&

{name}

+ }

{title}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx index 1d520d10..2423dfe7 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx @@ -1,17 +1,17 @@ import * as z from "zod"; -export const slideLayoutId = "education-image-gallery-slide"; -export const slideLayoutName = "Education Image Gallery Slide"; +export const slideLayoutId = "image-gallery-slide"; +export const slideLayoutName = "Image Gallery Slide"; export const slideLayoutDescription = - "A slide with a left image collage (one repeated image) and right text block for gallery heading and description."; + "A slide with a left image gallery and right text block for gallery heading and description."; export const Schema = z.object({ - title: z.string().min(3).max(16).default("Image Gallery").meta({ + title: z.string().max(24).default("Image Gallery").meta({ description: "Heading on the right side.", }), - body: z.string().min(70).max(200).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris." + body: z.string().max(300).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ).meta({ description: "Supporting paragraph shown below the heading.", }), diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx index 3a75cfae..51532365 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx @@ -5,10 +5,10 @@ import EducationChartPrimitives, { type EducationChartType, } from "./EducationChartPrimitives"; -export const slideLayoutId = "education-report-chart-slide"; -export const slideLayoutName = "Education Report Chart Slide"; +export const slideLayoutId = "report-chart-slide"; +export const slideLayoutName = "Report Chart Slide"; export const slideLayoutDescription = - "A split education report slide with one unified schema that supports multiple Recharts chart types in the right panel."; + "A left text column with a report title, body, footnote and a right-side chart."; const ChartTypeSchema = z.enum([ "bar", @@ -25,7 +25,7 @@ const SimpleDataSchema = z.object({ name: z.string().min(1).max(20).meta({ description: "Simple chart category label.", }), - value: z.number().min(-100000).max(100000).meta({ + value: z.number().meta({ description: "Simple chart numeric value.", }), }); @@ -71,7 +71,7 @@ const UnifiedChartDataSchema = z.union([ ]); export const Schema = z.object({ - title: z.string().min(3).max(20).default("Report").meta({ + title: z.string().max(24).default("Report").meta({ description: "Left-side report title.", }), body: z.string().min(80).max(260).default( diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx index d491e193..634a4ac4 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx @@ -1,13 +1,13 @@ import * as z from "zod"; -export const slideLayoutId = "education-services-split-slide"; -export const slideLayoutName = "Education Services Split Slide"; +export const slideLayoutId = "services-split-slide"; +export const slideLayoutName = "Services Split Slide"; export const slideLayoutDescription = - "A services layout with left heading, one repeated image column, and two stacked service description blocks on the right."; + "A left text column with a heading, one image column, and stacked service description blocks on the right side."; const ServiceSchema = z.object({ - serviceImage: z.object({ + image: z.object({ __image_url__: z.string(), __image_prompt__: z.string(), }).default({ @@ -15,30 +15,30 @@ const ServiceSchema = z.object({ "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Team meeting image reused across two rows", }).meta({ - description: "Single image reused in the middle column.", + description: "Single image in the middle column.", }), heading: z.string().min(3).max(18).meta({ - description: "Service heading shown in the right column.", + description: "Heading shown in the right column.", }), tagline: z.string().min(3).max(12).meta({ - description: "Short label under each service heading.", + description: "Short label under each heading.", }), body: z.string().max(40).meta({ - description: "Service description paragraph.", + description: "Description paragraph shown below the heading and tagline.", }), }); export const Schema = z.object({ - title: z.string().min(4).max(12).default("Services").meta({ + title: z.string().max(16).default("Services").meta({ description: "Main slide title shown on the left.", }), sections: z .array(ServiceSchema) - .min(2) + .min(1) .max(4) .default([ { - serviceImage: { + image: { __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Team meeting image reused across two rows", @@ -48,7 +48,7 @@ export const Schema = z.object({ body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", }, { - serviceImage: { + image: { __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Team meeting image reused across two rows", @@ -58,7 +58,7 @@ export const Schema = z.object({ body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", }, { - serviceImage: { + image: { __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Team meeting image reused across two rows", @@ -68,7 +68,7 @@ export const Schema = z.object({ body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor.", }, { - serviceImage: { + image: { __image_url__: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80", __image_prompt__: "Team meeting image reused across two rows", @@ -128,8 +128,8 @@ const EducationServicesSplitSlide = ({ data }: { data: Partial }) => > {section.serviceImage.__image_prompt__}
diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx index eefd401e..46ac18a1 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx @@ -1,27 +1,27 @@ import * as z from "zod"; -export const slideLayoutId = "education-statistics-grid-slide"; -export const slideLayoutName = "Education Statistics Grid Slide"; +export const slideLayoutId = "statistics-grid-slide"; +export const slideLayoutName = "Statistics Grid Slide"; export const slideLayoutDescription = - "A two-column layout with a left title block and a right 2x4 grid of statistics cards, using one subtle background image texture."; + "A left text column with a title, description and a right-side grid of statistics cards,value and label each in a card"; const StatisticSchema = z.object({ - value: z.string().min(1).max(8).meta({ - description: "Main metric value shown at the top of one card. with max 8 characters", + value: z.string().max(8).meta({ + description: "Main metric value shown at the top of one card.", }), - label: z.string().min(3).max(20).meta({ - description: "Label shown under the value. with max 20 characters", + label: z.string().max(20).meta({ + description: "Label shown under the value.", }), }); export const Schema = z.object({ - title: z.string().min(4).max(14).default("Statistics").meta({ - description: "Main title shown in the left column. with max 14 characters", + title: z.string().max(16).default("Statistics").meta({ + description: "Main title shown in the left column.", }), - description: z.string().min(40).max(120).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. with max 120 characters" + description: z.string().max(160).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit." ).meta({ - description: "Supporting line shown under the left title. with max 120 characters", + description: "Supporting line shown under the left title.", }), stats: z .array(StatisticSchema) @@ -38,7 +38,7 @@ export const Schema = z.object({ { value: "20", label: "Merchandising Team" }, ]) .meta({ - description: "Eight statistic cards. with max 8 cards", + description: "statistic cards, with value and label each in a card", }), }); @@ -58,10 +58,8 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) = fontFamily: "var(--body-font-family,'Times New Roman')", }} > - -
-
+

{data.title} @@ -89,22 +87,7 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) = ))}

} - {/* {stats && stats?.length > 4 && stats?.length <= 8 &&
- {stats?.map((stat, index) => ( -
-

- {stat.value} -

-

- {stat.label} -

-
- ))} -
} */} + {data.stats && data.stats?.length > 4 && data.stats?.length <= 8 && (() => { const rightArray = data.stats?.slice(0, Math.floor(data.stats?.length / 2)); @@ -112,7 +95,7 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) = return (
-
+
{leftArray?.map((stat: any, index: number) => (
}) = className="px-[52px] pt-[22px] h-full" style={{ backgroundColor: index % 2 === 0 ? 'var(--card-color,#5C0FD908)' : 'var(--card-color,white)' }} > -

+

{stat?.value}

@@ -129,9 +112,7 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) =

))}
- - -
+
{rightArray?.map((stat: any, index: number) => (
}) = className="px-[52px] pt-[22px] h-full" style={{ backgroundColor: index % 2 === 1 ? 'var(--card-color,#5C0FD908)' : 'var(--card-color,white)' }} > -

+

{stat.value}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx index f7d900b2..9204630b 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx @@ -1,8 +1,8 @@ import * as z from "zod"; -export const slideLayoutId = "education-table-of-contents-slide"; -export const slideLayoutName = "Education Table Of Contents Slide"; +export const slideLayoutId = "table-of-contents-slide"; +export const slideLayoutName = "Table Of Contents Slide"; export const slideLayoutDescription = "A split layout with a left title panel and a right list of numbered sections, with one subtle background image overlay."; @@ -16,28 +16,27 @@ const TocItemSchema = z.object({ }); export const Schema = z.object({ - titleLine1: z.string().min(4).max(12).default("Table of").meta({ - description: "First line of the left-side heading.", - }), - titleLine2: z.string().min(4).max(12).default("Contents").meta({ - description: "Second line of the left-side heading.", + title: z.string().min(6).max(32).default("Table of Contents").meta({ + description: "Main centered title of the table of contents slide.", }), items: z .array(TocItemSchema) - .min(8) - .max(8) + .min(1) + .max(10) .default([ - { number: "03", label: "ABOUT" }, - { number: "04", label: "TIMELINE" }, - { number: "05", label: "GROUP OF COMPANIES" }, - { number: "06", label: "SERVICES" }, - { number: "07", label: "IMAGE GALLERY" }, - { number: "08", label: "STATISTICS" }, - { number: "09", label: "REPORT" }, - { number: "10", label: "CONCLUSION" }, + { number: "01", label: "ABOUT" }, + { number: "02", label: "TIMELINE" }, + { number: "03", label: "GROUP OF COMPANIES" }, + { number: "04", label: "SERVICES" }, + { number: "05", label: "IMAGE GALLERY" }, + { number: "06", label: "STATISTICS" }, + { number: "07", label: "REPORT" }, + { number: "08", label: "CONCLUSION" }, + { number: "09", label: "QUESTIONS" }, + { number: "10", label: "CONTACT" }, ]) .meta({ - description: "Eight table-of-content entries listed on the right.", + description: "table-of-content entries listed on the right.", }), }); @@ -59,9 +58,7 @@ const EducationTableOfContentsSlide = ({ data }: { data: Partial })

- {data.titleLine1} -
- {data.titleLine2} + {data.title}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx index 86b1fcc8..85777e53 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx @@ -1,16 +1,16 @@ import * as z from "zod"; -export const slideLayoutId = "education-timeline-slide"; -export const slideLayoutName = "Education Timeline Slide"; +export const slideLayoutId = "timeline-slide"; +export const slideLayoutName = "Timeline Slide"; export const slideLayoutDescription = - "A timeline slide with a title, a horizontal progress line, and year-based milestones with short descriptions."; + "A slide with a title, a horizontal progress line, and short heading and description pairs."; const MilestoneSchema = z.object({ - year: z.string().min(4).max(6).meta({ - description: "Year label displayed under each timeline marker.", + heading: z.string().max(6).meta({ + description: "Heading displayed under each timeline marker.", }), - description: z.string().min(20).max(50).meta({ - description: "Short text shown under each year label.", + description: z.string().max(50).meta({ + description: "Short text shown under each heading. with max 50 characters", }), }); @@ -20,20 +20,17 @@ export const Schema = z.object({ }), milestones: z .array(MilestoneSchema) - .min(6) + .min(2) .max(12) .default([ - { year: "2022", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1994", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1993", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1991", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1991", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " }, - { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, - { year: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "2022", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "1994", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "1993", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "1991", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "1991", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " }, + { heading: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, + { heading: "1988", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, ]) .meta({ description: "Timeline milestones displayed left to right.", @@ -50,7 +47,7 @@ const EducationTimelineSlide = ({ data }: { data: Partial }) => { return (
+
{milestones.map((milestone: any, index: number) => ( -
+
{index !== milestones.length - 1 &&
}

- {milestone.year} + {milestone.heading}

{milestone.description} @@ -130,7 +127,7 @@ function TimelineMoreThanSix({ style={{ gridTemplateColumns: `repeat(${topItems.length}, minmax(0, 1fr))` }} > {topItems.map((milestone: any, index: number) => ( -

+
@@ -139,7 +136,7 @@ function TimelineMoreThanSix({

- {milestone.year} + {milestone.heading}

{milestone.description} @@ -162,7 +159,7 @@ function TimelineMoreThanSix({ if (!item) return

; return ( -
+
{/* {colIndex === 0 &&
} */}
@@ -171,7 +168,7 @@ function TimelineMoreThanSix({

- {item.year} + {item.heading}

{item.description} diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx index 2d3e2191..f0e05180 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx @@ -2,16 +2,16 @@ import * as z from "zod"; -export const slideLayoutId = "product-overview-business-challenges-cards-slide"; -export const slideLayoutName = "Product Overview Business Challenges Cards Slide"; +export const slideLayoutId = "title-description-with-cards-text-slide"; +export const slideLayoutName = "Title Description with Cards to Text Slide"; export const slideLayoutDescription = - "A business challenges slide with headline and tagline on the left, a large supporting image in the top-right area, and three vertical detail cards across the lower center-right section."; + "A slide with a title on top and a description below, and a content section containing cards of text."; const CardSchema = z.object({ - heading: z.string().min(4).max(12).meta({ + heading: z.string().max(16).meta({ description: "Card heading for one challenge column.", }), - body: z.string().max(40).meta({ + body: z.string().max(45).meta({ description: "Card body copy for one challenge column.", }), dark: z.boolean().default(false).meta({ @@ -20,13 +20,13 @@ const CardSchema = z.object({ }); export const Schema = z.object({ - title: z.string().min(8).max(24).default("Business Challenges").meta({ - description: "Main slide title.", + title: z.string().min(8).max(16).default("Business Challenges").meta({ + description: "Main slide title. Max 16 characters.", }), - taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ + taglineLabel: z.string().max(16).default("TAGLINE").meta({ description: "Short label above the left-side paragraph.", }), - taglineBody: z.string().max(80).default( + taglineBody: z.string().max(100).default( "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea." ).meta({ description: "Supporting paragraph on the left side.", @@ -76,13 +76,13 @@ const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) = <>

-
+

; const BusinessChallengesGridSlide = ({ data }: { data: Partial }) => { - const { title, challenges } = data; + const { title, blocks } = data; return ( <>
}) => className="grid grid-cols-2 justify-between items-center flex-1 gap-y-[63px] px-[84px] py-[70px] gap-x-[63px]" style={{ backgroundColor: "var(--primary-color,#15342D)" }} > - {challenges?.map((challenge, index) => ( + {blocks?.map((block, index) => (

- {challenge.heading} + {block.heading}

- {challenge.body} + {block.body}

))} diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx index a2ac164a..aad81772 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -1,25 +1,52 @@ import * as z from "zod"; -export const slideLayoutId = "product-overview-comparison-chart-slide"; -export const slideLayoutName = "Product Overview Comparison Chart Slide"; +export const slideLayoutId = "title-description-with-table-slide"; +export const slideLayoutName = "Title Description with Table Slide"; export const slideLayoutDescription = - "A comparison table slide with a headline, short description, four column headers, and three data rows using check, cross, or empty cells."; + "A slide with a title on top and a description below, and a content section containing a table with column headers and rows of text content."; const CellStatusSchema = z.enum(["check", "cross", "empty"]); -const RowSchema = z.object({ - label: z.string().min(4).max(18).meta({ +const GeneralRowSchema = z.object({ + label: z.string().max(18).meta({ description: "Row heading shown in the first column.", }), - cell1: CellStatusSchema.default("check"), - cell2: CellStatusSchema.default("empty"), - cell3: CellStatusSchema.default("check"), - cell4: CellStatusSchema.default("empty"), + cells: z.array(CellStatusSchema).min(1).max(8).meta({ + description: "Status cells aligned with the table columns.", + }), }); +const LegacyRowSchema = z.object({ + label: z.string().max(18).meta({ + description: "Row heading shown in the first column.", + }), + cell1: CellStatusSchema.optional(), + cell2: CellStatusSchema.optional(), + cell3: CellStatusSchema.optional(), + cell4: CellStatusSchema.optional(), +}); + +const RowSchema = z.union([GeneralRowSchema, LegacyRowSchema]); + +const DEFAULT_COLUMNS = ["HEADING 1", "HEADING 2", "HEADING 3", "HEADING 4"]; +const DEFAULT_ROWS: z.infer[] = [ + { + label: "HEADING 1", + cells: ["check", "cross", "check", "cross"], + }, + { + label: "HEADING 1", + cells: ["check", "empty", "check", "empty"], + }, + { + label: "HEADING 2", + cells: ["check", "check", "check", "check"], + }, +]; + export const Schema = z.object({ - title: z.string().min(8).max(20).default("Comparison Chart").meta({ + title: z.string().max(14).default("Comparison Chart").meta({ description: "Main heading shown above the table.", }), subtitle: z.string().max(80).default( @@ -28,42 +55,23 @@ export const Schema = z.object({ description: "Short subtitle shown under the main heading.", }), columns: z - .array(z.string().min(4).max(18)) - .min(4) + .array(z.string().max(18)) + .min(1) .max(4) - .default(["HEADING 1", "HEADING 1", "HEADING 2", "HEADING 3"]) + .default(DEFAULT_COLUMNS) .meta({ - description: "Four table column headings.", + description: "Table column headings.", }), + highlightedColumnIndex: z.number().int().min(1).max(8).default(4).meta({ + description: "1-based column index for the dark highlighted table header.", + }), rows: z .array(RowSchema) - + .min(1) .max(3) - .default([ - { - label: "HEADING 1", - cell1: "check", - cell2: "cross", - cell3: "check", - cell4: "cross", - }, - { - label: "HEADING 1", - cell1: "check", - cell2: "empty", - cell3: "check", - cell4: "empty", - }, - { - label: "HEADING 2", - cell1: "check", - cell2: "check", - cell3: "check", - cell4: "check", - }, - ]) + .default(DEFAULT_ROWS) .meta({ - description: "Three table rows with status indicators.", + description: "Table rows with status indicators. Prefer the `cells` array format.", }), checkIcon: z.object({ __icon_url__: z.string(), @@ -88,6 +96,7 @@ export const Schema = z.object({ }); export type SchemaType = z.infer; +type CellStatus = z.infer; function StatusIcon({ status, @@ -114,13 +123,46 @@ function StatusIcon({ } const ComparisonChartSlide = ({ data }: { data: Partial }) => { - const { title, subtitle, columns, rows, checkIcon, crossIcon } = data; + const { + title, + subtitle, + columns, + highlightedColumnIndex, + rows, + checkIcon, + crossIcon, + } = data; + const safeColumns = columns && columns.length > 0 ? columns : DEFAULT_COLUMNS; + const resolvedHighlightedColumnIndex = + highlightedColumnIndex && + highlightedColumnIndex >= 1 && + highlightedColumnIndex <= safeColumns.length + ? highlightedColumnIndex + : Math.min(4, safeColumns.length); + const safeRows = rows && rows.length > 0 ? rows : DEFAULT_ROWS; + const normalizedRows = safeRows.map((row) => { + const rowCells = + "cells" in row + ? row.cells + : [row.cell1, row.cell2, row.cell3, row.cell4].filter( + (cell): cell is CellStatus => typeof cell !== "undefined" + ); + + return { + label: row.label, + cells: Array.from( + { length: safeColumns.length }, + (_, cellIndex) => rowCells[cellIndex] ?? "empty" + ), + }; + }); + const tableGridColumns = `220px repeat(${safeColumns.length}, minmax(0, 1fr))`; return ( <>
}) => {

-
+
-
- {columns?.map((column, index) => ( +
+ {safeColumns.map((column, index) => (
}) => { ))}
- {rows?.map((row, index) => { - const cells: ("check" | "cross" | "empty")[] = [ - row.cell1, - row.cell2, - row.cell3, - row.cell4, - ]; - + {normalizedRows.map((row, index) => { return (
}) => { {row.label}
- {cells?.map((status, cellIndex) => ( + {row.cells.map((status, cellIndex) => (
[] = [ + { + cells: [ + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + ], + }, + { + cells: [ + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + ], + }, + { + cells: [ + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + "Lorem ipsum dolor sit.", + ], + }, +]; + export const Schema = z.object({ - title: z.string().min(8).max(20).default("Comparison Chart").meta({ + title: z.string().max(14).default("Comparison Chart").meta({ description: "Main heading shown above the table.", }), subtitle: z @@ -34,41 +66,23 @@ export const Schema = z.object({ description: "Short subtitle shown under the main heading.", }), columns: z - .array(z.string().max(10)) - .max(4) - .default(["HEADING 1", "HEADING 1", "HEADING 2", "HEADING 3"]) + .array(z.string().max(20)) + .min(1) + .max(8) + .default(DEFAULT_COLUMNS) .meta({ - description: "Four table column headings.", + description: "Table column headings.", }), - highlightedHeaderIndex: z.number().int().min(1).max(4).default(4).meta({ + highlightedHeaderIndex: z.number().int().min(1).max(8).default(4).meta({ description: "1-based column index for the dark highlighted table header.", }), rows: z .array(RowSchema) - .min(3) - .max(3) - .default([ - { - cell1: "Lorem ipsum dolor sit.", - cell2: "Lorem ipsum dolor sit.", - cell3: "Lorem ipsum dolor sit.", - cell4: "Lorem ipsum dolor sit.", - }, - { - cell1: "Lorem ipsum dolor sit.", - cell2: "Lorem ipsum dolor sit.", - cell3: "Lorem ipsum dolor sit.", - cell4: "Lorem ipsum dolor sit.", - }, - { - cell1: "Lorem ipsum dolor sit.", - cell2: "Lorem ipsum dolor sit.", - cell3: "Lorem ipsum dolor sit.", - cell4: "Lorem ipsum dolor sit.", - }, - ]) + .min(1) + .max(6) + .default(DEFAULT_ROWS) .meta({ - description: "Three table rows of text content.", + description: "Table rows of text content. Prefer the `cells` array format.", }), }); @@ -76,12 +90,33 @@ export type SchemaType = z.infer; const ComparisonTableWithTextSlide = ({ data }: { data: Partial }) => { const { title, subtitle, columns, highlightedHeaderIndex, rows } = data; + const safeColumns = columns && columns.length > 0 ? columns : DEFAULT_COLUMNS; + const resolvedHighlightedHeaderIndex = + highlightedHeaderIndex && + highlightedHeaderIndex >= 1 && + highlightedHeaderIndex <= safeColumns.length + ? highlightedHeaderIndex + : Math.min(4, safeColumns.length); + const safeRows = rows && rows.length > 0 ? rows : DEFAULT_ROWS; + const normalizedRows = safeRows.map((row) => { + const rowCells = + "cells" in row + ? row.cells + : [row.cell1, row.cell2, row.cell3, row.cell4].filter( + (cell): cell is string => typeof cell === "string" + ); + + return Array.from( + { length: safeColumns.length }, + (_, cellIndex) => rowCells[cellIndex] ?? "" + ); + }); return ( <>
}) = > - {columns?.map((column, index) => { - const isHighlighted = index + 1 === highlightedHeaderIndex; + {safeColumns.map((column, index) => { + const isHighlighted = index + 1 === resolvedHighlightedHeaderIndex; return ( }) = - {rows?.map((row, rowIndex) => { - const cells = [row.cell1, row.cell2, row.cell3, row.cell4]; - const isHighlighted = rowIndex + 1 === highlightedHeaderIndex; - + {normalizedRows.map((cells, rowIndex) => { return ( {cells?.map((cell, cellIndex) => ( @@ -148,12 +180,8 @@ const ComparisonTableWithTextSlide = ({ data }: { data: Partial }) = className=" border-r border-t bg-white p-[33px] text-left text-[18px] leading-[1.2] last:border-r-0" style={{ borderColor: "var(--stroke,#bcc3c3)", - backgroundColor: isHighlighted - ? "var(--primary-color,#05443a)" - : "var(--card-color,#ffffff)", - color: isHighlighted - ? "var(--primary-text,#eef2f0)" - : "var(--primary-color,#123f38)", + backgroundColor: "var(--card-color,#ffffff)", + color: "var(--primary-color,#123f38)", }} > {cell} diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx index 22a02183..acb54d8f 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/CoverSlide.tsx @@ -2,23 +2,23 @@ import * as z from "zod"; -export const slideLayoutId = "product-overview-cover-slide"; -export const slideLayoutName = "Product Overview Cover Slide"; +export const slideLayoutId = "cover-slide"; +export const slideLayoutName = "Cover Slide"; export const slideLayoutDescription = - "A cover slide with a compact logo in the top-left, a date in the top-right, a two-line centered title, and a city/building image anchored to the bottom with a soft fade into the background."; + "A cover slide with a compact logo in the top-left, a date/text/label in the top-right, a centered title, and a image anchored to the bottom with a soft fade into the background."; export const Schema = z.object({ - logoImage: z.object({ + 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().min(10).max(100).default("Professional logo of the company"), - }).default({ + __image_prompt__: z.string().default("Image of the company"), + }).optional().default({ __image_url__: "https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg", - __image_prompt__: "Professional logo of the company", + __image_prompt__: "Image of the company", }), - label: z.string().min(3).max(16).default("MARCH 2026").meta({ - description: "Date label shown at the top-right corner.", + label: z.string().min(3).max(16).optional().default("MARCH 2026").meta({ + description: "Date/text/label shown at the top-right corner.", }), titleLine1: z.string().min(3).max(18).default("Social Media").meta({ description: "First line of the cover title.", @@ -39,13 +39,12 @@ export type SchemaType = z.infer; const CoverSlide = ({ data }: { data: Partial }) => { - const { logoImage, label, titleLine1, titleLine2, backgroundImage } = data; return ( <>
}) => { >
- {logoImage?.__image_prompt__ + /> :

}

- {label} + {data.label || ''}

@@ -75,17 +74,17 @@ const CoverSlide = ({ data }: { data: Partial }) => {

- {titleLine1} + {data.titleLine1}
- {titleLine2} + {data.titleLine2}

- {backgroundImage?.__image_url__ && ( + {data.backgroundImage?.__image_url__ && ( {backgroundImage.__image_prompt__ )} diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx index b8e49b0f..cf5c27a0 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx @@ -1,17 +1,17 @@ import * as z from "zod"; -export const slideLayoutId = "product-overview-image-gallery-slide"; -export const slideLayoutName = "Product Overview Image Gallery Slide"; +export const slideLayoutId = "title-description-with-image-gallery-slide"; +export const slideLayoutName = "Title Description with Image Gallery Slide"; export const slideLayoutDescription = - "A gallery slide with a title and paragraph on the left and a five-image collage on the right and bottom."; + "A text slide with a title on top and a description below, and a section containing a gallery of images."; export const Schema = z.object({ title: z.string().max(12).default("Image Gallery").meta({ - description: "Main gallery heading.", + description: "Main Title of the slide", }), - description: z.string().max(80).default( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." + description: z.string().max(120).default( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." ).meta({ description: "Supporting paragraph shown under the title.", }), @@ -88,7 +88,7 @@ const ImageGallerySlide = ({ data }: { data: Partial }) => { <>
}) => {
-
+
{bottomWideImage?.__image_prompt__} }) => { <>
}) => { <>
}) => { className="flex h-[55px] w-[55px] items-center justify-center rounded-full bg-[#15342D]" style={{ backgroundColor: "var(--primary-color,#15342D)" }} > - {kpiIcon?.__icon_query__} */} + +

}) => { <>

}) => { <>
}) => { className="h-[244px] w-full object-cover" />
; const MissionVisionSlide = ({ data }: { data: Partial }) => { - const { title, missionLabel, missionBody, visionLabel, visionBody, image } = data; return ( <>
}) => { className="text-[80px] font-semibold leading-[108.4%] tracking-[-2.419px] text-[#15342D]" style={{ color: "var(--primary-color,#15342D)" }} > - {title} + {data.title}

@@ -71,13 +70,13 @@ const MissionVisionSlide = ({ data }: { data: Partial }) => { className="text-[20px] font-semibold tracking-[2.074px] text-white" style={{ color: "var(--primary-text,#edf2f1)" }} > - {missionLabel} + {data.topleftTextBlockLabel}

- {missionBody} + {data.topleftTextBlockBody}

@@ -89,24 +88,23 @@ const MissionVisionSlide = ({ data }: { data: Partial }) => { className="text-[20px] font-semibold tracking-[2.074px] text-white" style={{ color: "var(--primary-text,#edf2f1)" }} > - {visionLabel} + {data.bottomleftTextBlockLabel}

- {visionBody} + {data.bottomleftTextBlockBody}

- - {image?.__image_url__ && ( + {data.image?.__image_url__ && ( {image.__image_prompt__} )} diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx index 25d76baf..d2adc108 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/OurServicesSlide.tsx @@ -1,28 +1,28 @@ import * as z from "zod"; -export const slideLayoutId = "product-overview-our-services-slide"; -export const slideLayoutName = "Product Overview Our Services Slide"; +export const slideLayoutId = "title-description-with-image-block-slide"; +export const slideLayoutName = "Title Description with Image Block Slide"; export const slideLayoutDescription = - "A services slide with title and intro copy on the left, a large image below, and a two-by-two service card matrix on the right."; + "A slide with a title on top and a description below, and a content section containing an image and a grid of cards of text."; -const ServiceSchema = z.object({ - heading: z.string().min(4).max(12).meta({ - description: "Service card heading.", +const CardSchema = z.object({ + heading: z.string().max(16).meta({ + description: "Card heading.", }), body: z.string().max(30).meta({ - description: "Service card short description.", + description: "Card short description.", }), - dark: z.boolean().default(false).meta({ - description: "Whether this service card uses the dark style.", + isHighlighted: z.boolean().default(false).meta({ + description: "Whether this card uses the dark style.", }), }); export const Schema = z.object({ - title: z.string().min(6).max(12).default("Our Services").meta({ + title: z.string().max(16).default("Our Services").meta({ description: "Main heading shown at the top-left.", }), - taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({ + taglineLabel: z.string().max(16).default("TAGLINE").meta({ description: "Small label above left paragraph.", }), taglineBody: z.string().max(30).default( @@ -41,17 +41,17 @@ export const Schema = z.object({ description: "Main image shown at the lower left side.", }), services: z - .array(ServiceSchema) + .array(CardSchema) .max(4) .default([ - { heading: "HEADING 1", body: "Lorem ipsum dolor sit amet, consectetur", dark: false }, - { heading: "HEADING 2", body: "Lorem ipsum dolor sit amet, consectetur", dark: true }, - { heading: "HEADING 3", body: "Lorem ipsum dolor sit amet, consectetur", dark: false }, - { heading: "HEADING 4", body: "Lorem ipsum dolor sit amet, consectetur", dark: false }, + { heading: "HEADING 1", body: "Lorem ipsum dolor sit amet, consectetur", isHighlighted: false }, + { heading: "HEADING 2", body: "Lorem ipsum dolor sit amet, consectetur", isHighlighted: true }, + { heading: "HEADING 3", body: "Lorem ipsum dolor sit amet, consectetur", isHighlighted: false }, + { heading: "HEADING 4", body: "Lorem ipsum dolor sit amet, consectetur", isHighlighted: false }, ]) .meta({ - description: "Four service cards rendered on the right side.", + description: "Cards rendered on the right side.", }), }); @@ -64,7 +64,7 @@ const OurServicesSlide = ({ data }: { data: Partial }) => { <>
}) => {
- {services?.map((service, index) => ( + {services?.map((card, index) => (
}) => {

- {service.heading} + {card.heading}

- {service.body} + {card.body}

))} diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx index 95b10c34..370c360c 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/PricingPlanSlide.tsx @@ -1,10 +1,11 @@ +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; import * as z from "zod"; -export const slideLayoutId = "product-overview-pricing-plan-slide"; -export const slideLayoutName = "Product Overview Pricing Plan Slide"; +export const slideLayoutId = "title-cards-list-with-text-slide"; +export const slideLayoutName = "Title with Cards List with Text"; export const slideLayoutDescription = - "A three-column pricing slide with one emphasized center plan and feature bullet lists for each plan."; + "A slide with a title on top and a content section containing a list of cards with text content."; const PlanSchema = z.object({ price: z.string().min(4).max(12).meta({ @@ -14,7 +15,7 @@ const PlanSchema = z.object({ description: "Short statement describing the plan.", }), features: z - .array(z.string().max(14)) + .array(z.string().max(16)) .max(4) .meta({ @@ -91,7 +92,7 @@ const PricingPlanSlide = ({ data }: { data: Partial }) => { <>
}) => {
{plan.features.map((feature, featureIndex) => (
- + {/* {featureIcon?.__icon_query__} + /> */}

}) => { <>

-
+

}) => { {title}

- - - -
+
{steps?.map((step, index) => { if (index % 2 === 0) { return ( @@ -109,11 +107,17 @@ const ProcessSlide = ({ data }: { data: Partial }) => { >
- {step.icon.__icon_query__} +
+ + +
+ @@ -168,7 +172,15 @@ const ProcessSlide = ({ data }: { data: Partial }) => { - {step.icon.__icon_query__} +
+ +
diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx index d158a8e6..34d31063 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ReportSnapshotSlide.tsx @@ -13,11 +13,12 @@ import { Line, LabelList, } from "recharts"; +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; -export const slideLayoutId = "product-overview-report-snapshot-slide"; -export const slideLayoutName = "Product Overview Report Snapshot Slide"; +export const slideLayoutId = "title-description-with-chart-and-kpi-cards-slide"; +export const slideLayoutName = "Title Description with Chart and KPI Cards Slide"; export const slideLayoutDescription = - "A report summary slide with a left-edge photo strip, title and intro copy, a chart card that supports four visual styles, and KPI callout cards on the right."; + "A text slide with a title on top and a description below, and a content section containing a chart and a grid of KPI cards."; const LegacyBarSchema = z.object({ value: z.number().min(10).max(100).meta({ @@ -335,372 +336,380 @@ const ReportSnapshotSlide = ({ data }: { data: Partial }) => { <>
- {sideImage?.__image_url__ && ( - {sideImage.__image_prompt__} - )} +
-
-

- {title} -

-
-

+ )} +

+
- {taglineLabel} -

-

- {taglineBody} -

-
-
+

+ {title} +

-
-

- {chartTitle} -

- - {activeChartStyle === "mini-bars" && ( - <> -
- - - - - `$${value}`} - axisLine={false} - tickLine={false} - tick={{ fill: "var(--background-text,#6C7271)", fontSize: 10 }} - /> - - - - -
- -
+

- {footerLabel} + {taglineLabel}

+ {taglineBody} +

+
+
+
+
+

- {footerValue} + {chartTitle}

-
- - )} - {activeChartStyle === "donut" && ( -
-
- - - - {(donutData ?? []).map((entry, index) => ( - - ))} - - - -
-
- {(donutData ?? []).map((entry, index) => { - const percent = Math.round((entry.value / donutTotal) * 100); - return ( -
+ {activeChartStyle === "mini-bars" && ( + <> +
+ + + + + `$${value}`} + axisLine={false} + tickLine={false} + tick={{ fill: "var(--background-text,#6C7271)", fontSize: 10 }} + /> + + + + +
+ +
+

+ {footerLabel} +

+

+ {footerValue} +

+
+ + )} + + {activeChartStyle === "donut" && ( +
+
+ + + + {(donutData ?? []).map((entry, index) => ( + + ))} + + + +
+ +
+ {(donutData ?? []).map((entry, index) => { + const percent = Math.round((entry.value / donutTotal) * 100); + return ( +
+
+ +

+ {legendLabels?.[index] ?? entry.name} +

+
+

+ {percent}% +

+
+ ); + })} +
+
+ )} + + {activeChartStyle === "grouped-bars" && ( +
+
+ + + + + + + + + + + + + +
+ +

- {legendLabels?.[index] ?? entry.name} + {legendLabels?.[0] ?? "Option A"} +

+
+
+ +

+ {legendLabels?.[1] ?? "Option B"} +

+
+
+
+ )} + + {activeChartStyle === "dual-line" && ( +
+
+ + + + + + + + + +
+ +
+
+ +

+ {legendLabels?.[0] ?? "Option A"} +

+
+
+ +

+ {legendLabels?.[1] ?? "Option B"} +

+
+
+
+ )} +
+ +
+
+ {visibleMetricCards.map((metric, index) => ( +
+
+
+ {usePulseFallback ? ( + + ) : ( + + )} +
+

+ {metric.value}

- {percent}% + {metric.body}

- ); - })} -
-
- )} - - {activeChartStyle === "grouped-bars" && ( -
-
- - - - - - - - - - - - - -
- -
-
- -

- {legendLabels?.[0] ?? "Option A"} -

-
-
- -

- {legendLabels?.[1] ?? "Option B"} -

+ ))}
- )} - - {activeChartStyle === "dual-line" && ( -
-
- - - - - - - - - -
- -
-
- -

- {legendLabels?.[0] ?? "Option A"} -

-
-
- -

- {legendLabels?.[1] ?? "Option B"} -

-
-
-
- )} -
- -
-
- {visibleMetricCards.map((metric, index) => ( -
-
-
- {usePulseFallback ? ( - - ) : ( - {metricIcon?.__icon_query__} - )} -
-

- {metric.value} -

-
-

- {metric.body} -

-
- ))}
diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx index e1bbeb54..02549efc 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/TableOfContentSlide.tsx @@ -4,19 +4,19 @@ const PRODUCT_BG = "var(--background-color,#d7dddd)"; const PRODUCT_DARK = "var(--primary-color,#05463d)"; -export const slideLayoutId = "product-overview-table-of-content-slide"; -export const slideLayoutName = "Product Overview Table of Content Slide"; +export const slideLayoutId = "table-of-content-slide"; +export const slideLayoutName = "Table of Content Slide"; export const slideLayoutDescription = - "A two-column table-of-content slide with section titles and numbers on a dark left panel and a large title plus description paragraph on the right panel."; + "A two-column table of contents slide with section titles and numbers on a left panel and a title plus description paragraph on the right panel."; const SectionSchema = z.object({ - title: z.string().min(4).max(14).meta({ + title: z.string().min(4).max(25).meta({ description: "Section label shown in the left navigation column.", }), number: z.string().min(2).max(3).meta({ description: "Section number shown beside the section label.", }), - description: z.string().min(4).max(22).optional().meta({ + description: z.string().min(4).max(60).optional().meta({ description: "Section description shown in the right column.", }), }); @@ -25,22 +25,22 @@ export const Schema = z.object({ title: z.string().min(6).max(18).default("Table Of Content").meta({ description: "Heading in the right-side content area.", }), - description: z.string().min(50).max(120).default( + description: z.string().min(50).max(160).default( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore." ).meta({ description: "Supporting descriptive paragraph under the heading.", }), sections: z .array(SectionSchema) - .max(6) .default([ - { title: "SECTION TITLE", number: "01", description: "Lorem ipsum dolor sit." }, - { title: "SECTION TITLE", number: "02", description: "Lorem ipsum dolor sit." }, - { title: "SECTION TITLE", number: "03", description: "Lorem ipsum dolor sit." }, - { title: "SECTION TITLE", number: "04", description: "Lorem ipsum dolor sit." }, - { title: "SECTION TITLE", number: "05", description: "Lorem ipsum dolor sit." }, - { title: "SECTION TITLE", number: "06", description: "Lorem ipsum dolor sit." }, + { title: "SECTION TITLE SECTION TITLE", number: "01", description: "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit." }, + { title: "SECTION TITLE SECTION TITLE", number: "02", description: "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit." }, + { title: "SECTION TITLE SECTION TITLE", number: "03", description: "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit." }, + { title: "SECTION TITLE SECTION TITLE", number: "04", description: "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit." }, + { title: "SECTION TITLE SECTION TITLE", number: "05", description: "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit." }, + { title: "SECTION TITLE SECTION TITLE", number: "06", description: "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit." }, + ]) .meta({ description: "Six rows listed in the table of contents panel.", @@ -56,17 +56,17 @@ const TableOfContentSlide = ({ data }: { data: Partial }) => { <>
-
-
+
+
3 ? 'space-y-[28px]' : 'space-y-[40px]'}`}> {sections?.map((section, index) => ( -
+

Date: Thu, 9 Apr 2026 22:10:41 +0545 Subject: [PATCH 13/18] refactor: 'Report' tempalte filename changes --- ... => BarChartWithBulletListWithTitleDescriptionIconSlide.tsx} | 0 ...istSlide.tsx => BulletListWithIconTitleDescriptionSlide.tsx} | 0 ...ide.tsx => HorizontalHeightSpanningImagesWithTitleSlide.tsx} | 2 +- .../Report/{IntroSlide.tsx => IntroCoverSlide.tsx} | 0 .../Report/{IntroductionStatsSlide.tsx => MetricsSlide.tsx} | 0 ...isLineStatsSlide.tsx => TitleChartWithMetricsCardsSlide.tsx} | 0 ...alysisInsightBarSlide.tsx => TitleDescriptionChartSlide.tsx} | 0 .../{SolutionSlide.tsx => TitleImageBulletCardsSlide.tsx} | 0 .../{PerformanceSnapshotSlide.tsx => TitleMetricsSlide.tsx} | 0 ...icesSlide.tsx => TitleWorkflowWithTitleDescriptionSlide.tsx} | 0 10 files changed, 1 insertion(+), 1 deletion(-) rename electron/servers/nextjs/app/presentation-templates/Report/{DataAnalysisBarSlide.tsx => BarChartWithBulletListWithTitleDescriptionIconSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{DataAnalysisListSlide.tsx => BulletListWithIconTitleDescriptionSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{TeamSlide.tsx => HorizontalHeightSpanningImagesWithTitleSlide.tsx} (99%) rename electron/servers/nextjs/app/presentation-templates/Report/{IntroSlide.tsx => IntroCoverSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{IntroductionStatsSlide.tsx => MetricsSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{DataAnalysisLineStatsSlide.tsx => TitleChartWithMetricsCardsSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{DataAnalysisInsightBarSlide.tsx => TitleDescriptionChartSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{SolutionSlide.tsx => TitleImageBulletCardsSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{PerformanceSnapshotSlide.tsx => TitleMetricsSlide.tsx} (100%) rename electron/servers/nextjs/app/presentation-templates/Report/{ServicesSlide.tsx => TitleWorkflowWithTitleDescriptionSlide.tsx} (100%) diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/BarChartWithBulletListWithTitleDescriptionIconSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisBarSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/BarChartWithBulletListWithTitleDescriptionIconSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/BulletListWithIconTitleDescriptionSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisListSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/BulletListWithIconTitleDescriptionSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx similarity index 99% rename from electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx index 0cee7914..75bb188b 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TeamSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx @@ -113,7 +113,7 @@ const TeamSlide = ({ data }: { data: Partial }) => { {member.title}

- {member.name} + {member.subtext}

diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroCoverSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/IntroSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/IntroCoverSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MetricsSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/IntroductionStatsSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/MetricsSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleChartWithMetricsCardsSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisLineStatsSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/TitleChartWithMetricsCardsSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionChartSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisInsightBarSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionChartSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleImageBulletCardsSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/SolutionSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/TitleImageBulletCardsSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleMetricsSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/PerformanceSnapshotSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/TitleMetricsSlide.tsx diff --git a/electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleWorkflowWithTitleDescriptionSlide.tsx similarity index 100% rename from electron/servers/nextjs/app/presentation-templates/Report/ServicesSlide.tsx rename to electron/servers/nextjs/app/presentation-templates/Report/TitleWorkflowWithTitleDescriptionSlide.tsx From 009d29d12ab39e655eef5b87abaac8422ec617fa Mon Sep 17 00:00:00 2001 From: Suraj Jha Date: Thu, 9 Apr 2026 22:11:05 +0545 Subject: [PATCH 14/18] refactor: 'Report' tempalte filename changes --- .codex | 0 .../app/presentation-templates/index.tsx | 40 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 .codex diff --git a/.codex b/.codex new file mode 100644 index 00000000..e69de29b diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index 606af6cc..411255a9 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -46,19 +46,19 @@ import ReportSnapshotSlide, { Schema as PoReportSnapshotSchema, slideLayoutId as 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 ReportIntroSlide, { Schema as RepIntroSchema, slideLayoutId as RepIntroId, slideLayoutName as RepIntroName, slideLayoutDescription as RepIntroDesc } from "./Report/IntroCoverSlide"; import TitleDescriptionImageSlide, { Schema as RepIntroductionImageSchema, slideLayoutId as RepIntroductionImageId, slideLayoutName as RepIntroductionImageName, slideLayoutDescription as RepIntroductionImageDesc } from "./Report/TitleDescriptionImageSlide"; -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 IntroductionStatsSlide, { Schema as RepIntroductionStatsSchema, slideLayoutId as RepIntroductionStatsId, slideLayoutName as RepIntroductionStatsName, slideLayoutDescription as RepIntroductionStatsDesc } from "./Report/MetricsSlide"; +import SolutionSlide, { Schema as RepSolutionSchema, slideLayoutId as RepSolutionId, slideLayoutName as RepSolutionName, slideLayoutDescription as RepSolutionDesc } from "./Report/TitleImageBulletCardsSlide"; 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 DataAnalysisListSlide, { Schema as RepDataAnalysisListSchema, slideLayoutId as RepDataAnalysisListId, slideLayoutName as RepDataAnalysisListName, slideLayoutDescription as RepDataAnalysisListDesc } from "./Report/BulletListWithIconTitleDescriptionSlide"; +import DataAnalysisBarSlide, { Schema as RepDataAnalysisBarSchema, slideLayoutId as RepDataAnalysisBarId, slideLayoutName as RepDataAnalysisBarName, slideLayoutDescription as RepDataAnalysisBarDesc } from "./Report/BarChartWithBulletListWithTitleDescriptionIconSlide"; +import DataAnalysisInsightBarSlide, { Schema as RepDataAnalysisInsightBarSchema, slideLayoutId as RepDataAnalysisInsightBarId, slideLayoutName as RepDataAnalysisInsightBarName, slideLayoutDescription as RepDataAnalysisInsightBarDesc } from "./Report/TitleDescriptionChartSlide"; +import DataAnalysisLineStatsSlide, { Schema as RepDataAnalysisLineStatsSchema, slideLayoutId as RepDataAnalysisLineStatsId, slideLayoutName as RepDataAnalysisLineStatsName, slideLayoutDescription as RepDataAnalysisLineStatsDesc } from "./Report/TitleChartWithMetricsCardsSlide"; 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"; +import PerformanceSnapshotSlide, { Schema as RepPerformanceSnapshotSchema, slideLayoutId as RepPerformanceSnapshotId, slideLayoutName as RepPerformanceSnapshotName, slideLayoutDescription as RepPerformanceSnapshotDesc } from "./Report/TitleMetricsSlide"; +import ReportServicesSlide, { Schema as RepServicesSchema, slideLayoutId as RepServicesId, slideLayoutName as RepServicesName, slideLayoutDescription as RepServicesDesc } from "./Report/TitleWorkflowWithTitleDescriptionSlide"; +import ReportTeamSlide, { Schema as RepTeamSchema, slideLayoutId as RepTeamId, slideLayoutName as RepTeamName, slideLayoutDescription as RepTeamDesc } from "./Report/HorizontalHeightSpanningImagesWithTitleSlide"; // General templates import GeneralIntroSlideLayout, { Schema as GeneralIntroSchema, layoutId as GeneralIntroId, layoutName as GeneralIntroName, layoutDescription as GeneralIntroDesc } from "./general/IntroSlideLayout"; @@ -289,19 +289,19 @@ export const productOverviewTemplates: TemplateWithData[] = [ ]; export const reportTemplates: TemplateWithData[] = [ - createTemplateEntry(ReportIntroSlide, RepIntroSchema, RepIntroId, RepIntroName, RepIntroDesc, "report", "IntroSlide"), + createTemplateEntry(ReportIntroSlide, RepIntroSchema, RepIntroId, RepIntroName, RepIntroDesc, "report", "IntroCoverSlide"), createTemplateEntry(TitleDescriptionImageSlide, RepIntroductionImageSchema, RepIntroductionImageId, RepIntroductionImageName, RepIntroductionImageDesc, "report", "TitleDescriptionImageSlide"), - createTemplateEntry(IntroductionStatsSlide, RepIntroductionStatsSchema, RepIntroductionStatsId, RepIntroductionStatsName, RepIntroductionStatsDesc, "report", "IntroductionStatsSlide"), - createTemplateEntry(SolutionSlide, RepSolutionSchema, RepSolutionId, RepSolutionName, RepSolutionDesc, "report", "SolutionSlide"), + createTemplateEntry(IntroductionStatsSlide, RepIntroductionStatsSchema, RepIntroductionStatsId, RepIntroductionStatsName, RepIntroductionStatsDesc, "report", "MetricsSlide"), + createTemplateEntry(SolutionSlide, RepSolutionSchema, RepSolutionId, RepSolutionName, RepSolutionDesc, "report", "TitleImageBulletCardsSlide"), 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(DataAnalysisListSlide, RepDataAnalysisListSchema, RepDataAnalysisListId, RepDataAnalysisListName, RepDataAnalysisListDesc, "report", "BulletListWithIconTitleDescriptionSlide"), + createTemplateEntry(DataAnalysisBarSlide, RepDataAnalysisBarSchema, RepDataAnalysisBarId, RepDataAnalysisBarName, RepDataAnalysisBarDesc, "report", "BarChartWithBulletListWithTitleDescriptionIconSlide"), + createTemplateEntry(DataAnalysisInsightBarSlide, RepDataAnalysisInsightBarSchema, RepDataAnalysisInsightBarId, RepDataAnalysisInsightBarName, RepDataAnalysisInsightBarDesc, "report", "TitleDescriptionChartSlide"), + createTemplateEntry(DataAnalysisLineStatsSlide, RepDataAnalysisLineStatsSchema, RepDataAnalysisLineStatsId, RepDataAnalysisLineStatsName, RepDataAnalysisLineStatsDesc, "report", "TitleChartWithMetricsCardsSlide"), 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"), + createTemplateEntry(PerformanceSnapshotSlide, RepPerformanceSnapshotSchema, RepPerformanceSnapshotId, RepPerformanceSnapshotName, RepPerformanceSnapshotDesc, "report", "TitleMetricsSlide"), + createTemplateEntry(ReportServicesSlide, RepServicesSchema, RepServicesId, RepServicesName, RepServicesDesc, "report", "TitleWorkflowWithTitleDescriptionSlide"), + createTemplateEntry(ReportTeamSlide, RepTeamSchema, RepTeamId, RepTeamName, RepTeamDesc, "report", "HorizontalHeightSpanningImagesWithTitleSlide"), ]; export const neoGeneralTemplates: TemplateWithData[] = [ From 4ecab24add50feffd484097d39a9736000c22dcf Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Thu, 9 Apr 2026 22:54:41 +0545 Subject: [PATCH 15/18] feat: new template improvement --- .../Education/EducationCoverSlide.tsx | 8 ++-- .../BusinessChallengesCardsSlide.tsx | 2 +- .../BusinessChallengesGridSlide.tsx | 6 +-- .../ProductOverview/ComparisonChartSlide.tsx | 47 ++++++++++--------- .../ComparisonTableWithTextSlide.tsx | 2 +- .../ProductOverview/CoverSlide.tsx | 5 +- .../ProductOverview/ImageGallerySlide.tsx | 2 +- .../ProductOverview/KpiCardsSlide.tsx | 2 +- .../MarketOpportunitySlide.tsx | 2 +- .../ProductOverview/MeetTeamSlide.tsx | 6 +-- .../ProductOverview/MissionVisionSlide.tsx | 2 +- .../ProductOverview/OurServicesSlide.tsx | 2 +- .../ProductOverview/PricingPlanSlide.tsx | 6 +-- .../ProductOverview/ProcessSlide.tsx | 9 ++-- .../ProductOverview/TableOfContentSlide.tsx | 2 +- 15 files changed, 53 insertions(+), 50 deletions(-) diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx index d8a1fa2a..f1bdcc1f 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx @@ -4,11 +4,11 @@ import * as z from "zod"; export const slideLayoutId = "cover-slide"; export const slideLayoutName = "Cover Slide"; export const slideLayoutDescription = - "A full-bleed cover slide with a single background image, a strong violet overlay, and centered company/instructor/presenter/institute name and title text."; + "Opening/cover/intro slide with organization/institution/presenter, presentation title/heading , and supporting subtitle."; export const Schema = z.object({ - name: z.string().min(3).max(16).optional().default("COMPANY NAME").meta({ - description: "Company/instructor/presenter/institute name label shown above the main title.", + name: z.string().min(3).max(16).optional().default("Name").meta({ + description: "Optional organization/institution/presenter name shown above the slide title.", }), title: z.string().min(6).max(32).default("PowerPoint Template").meta({ description: "Main centered title of the cover slide.", @@ -57,7 +57,7 @@ const EducationCoverSlide = ({ data }: { data: Partial }) => { className="relative z-10 flex h-full flex-col items-center justify-center text-center" style={{ color: "var(--primary-text,#ffffff)" }} > - {name &&

{name}

+ {name &&

{name}

}

{title}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx index f0e05180..39726418 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesCardsSlide.tsx @@ -82,7 +82,7 @@ const BusinessChallengesCardsSlide = ({ data }: { data: Partial }) = fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) =>

{blocks?.map((block, index) => ( diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx index aad81772..514c1e58 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -4,7 +4,7 @@ import * as z from "zod"; export const slideLayoutId = "title-description-with-table-slide"; export const slideLayoutName = "Title Description with Table Slide"; export const slideLayoutDescription = - "A slide with a title on top and a description below, and a content section containing a table with column headers and rows of text content."; + "A slide with a title on top and a description below, and a content section containing a table with column headers and rows of check, cross and empty state of content."; const CellStatusSchema = z.enum(["check", "cross", "empty"]); @@ -29,21 +29,7 @@ const LegacyRowSchema = z.object({ const RowSchema = z.union([GeneralRowSchema, LegacyRowSchema]); -const DEFAULT_COLUMNS = ["HEADING 1", "HEADING 2", "HEADING 3", "HEADING 4"]; -const DEFAULT_ROWS: z.infer[] = [ - { - label: "HEADING 1", - cells: ["check", "cross", "check", "cross"], - }, - { - label: "HEADING 1", - cells: ["check", "empty", "check", "empty"], - }, - { - label: "HEADING 2", - cells: ["check", "check", "check", "check"], - }, -]; + export const Schema = z.object({ title: z.string().max(14).default("Comparison Chart").meta({ @@ -58,7 +44,7 @@ export const Schema = z.object({ .array(z.string().max(18)) .min(1) .max(4) - .default(DEFAULT_COLUMNS) + .default(["HEADING 1", "HEADING 2", "HEADING 3", "HEADING 4"]) .meta({ description: "Table column headings.", }), @@ -69,7 +55,20 @@ export const Schema = z.object({ .array(RowSchema) .min(1) .max(3) - .default(DEFAULT_ROWS) + .default([ + { + label: "HEADING 1", + cells: ["check", "cross", "check", "cross"], + }, + { + label: "HEADING 1", + cells: ["check", "empty", "check", "empty"], + }, + { + label: "HEADING 2", + cells: ["check", "check", "check", "check"], + }, + ]) .meta({ description: "Table rows with status indicators. Prefer the `cells` array format.", }), @@ -105,7 +104,7 @@ function StatusIcon({ crossIconUrl, crossIconAlt, }: { - status: "check" | "cross" | "empty"; + status: any; checkIconUrl: string | undefined; checkIconAlt: string | undefined; crossIconUrl: string | undefined; @@ -118,8 +117,12 @@ function StatusIcon({ if (status === "cross") { return {crossIconAlt}; } + if (status === 'check') { - return {checkIconAlt}; + + return {checkIconAlt}; + } + return

{status}

} const ComparisonChartSlide = ({ data }: { data: Partial }) => { @@ -168,7 +171,7 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => { {row.cells.map((status, cellIndex) => (
}) = fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => {

- {data.titleLine1} -
- {data.titleLine2} +

{data.titleLine1}

+

{data.titleLine2}

diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx index cf5c27a0..b14d590f 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ImageGallerySlide.tsx @@ -88,7 +88,7 @@ const ImageGallerySlide = ({ data }: { data: Partial }) => { <>
}) => { }} /> -
+

}) => { fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => { fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => {

-
+
{members?.map((member, index) => (
}) => { className="h-[244px] w-full object-cover" />
}) => { }} >
-
+

}) => { fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => { <>
-
+

}) => {

-
+
{plans?.map((plan, index) => { const active = plan.highlighted; return ( diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx index 275a6147..f8b848a1 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ProcessSlide.tsx @@ -88,7 +88,7 @@ const ProcessSlide = ({ data }: { data: Partial }) => { fontFamily: "var(--body-font-family,'Bricolage Grotesque')", }} > -
+

}) => {

-
+

}) => { return (

-
+

}) => { }} >

-
+
3 ? 'space-y-[28px]' : 'space-y-[40px]'}`}> {sections?.map((section, index) => (
From d3254143c2821230342956d505837b3e9683a229 Mon Sep 17 00:00:00 2001 From: Suraj Jha Date: Thu, 9 Apr 2026 23:33:23 +0545 Subject: [PATCH 16/18] update: 'Code' template updates --- ...sponse.tsx => APIRequestResponseSlide.tsx} | 16 +- ...de04FeatureGrid.tsx => CardsGridSlide.tsx} | 15 - .../Code/CodeExplanationSplitSlide.tsx | 264 ++++++++++++++++++ .../Code/CodeSlide02CodeExplanationSplit.tsx | 136 --------- .../Code/CodeSlide05ComparisonTable.tsx | 146 ---------- ...Slide01RoadmapCover.tsx => CoverSlide.tsx} | 14 - ...lit.tsx => DescriptionAndMetricsSlide.tsx} | 22 +- ...ationText.tsx => DescriptionTextSlide.tsx} | 14 - ...11MetricsGrid.tsx => MetricsGridSlide.tsx} | 15 - ...eOfContent.tsx => TableOfContentSlide.tsx} | 28 +- .../Code/TableSlide.tsx | 160 +++++++++++ ...eList.tsx => TwoColumnBulletListSlide.tsx} | 15 - ...eSlide06Workflow.tsx => WorkflowSlide.tsx} | 14 - .../app/presentation-templates/index.tsx | 44 +-- 14 files changed, 458 insertions(+), 445 deletions(-) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide03ApiRequestResponse.tsx => APIRequestResponseSlide.tsx} (91%) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide04FeatureGrid.tsx => CardsGridSlide.tsx} (91%) create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeExplanationSplitSlide.tsx delete mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx delete mode 100644 electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide01RoadmapCover.tsx => CoverSlide.tsx} (81%) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide10MetricsSplit.tsx => DescriptionAndMetricsSlide.tsx} (82%) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide08CodeExplanationText.tsx => DescriptionTextSlide.tsx} (79%) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide11MetricsGrid.tsx => MetricsGridSlide.tsx} (85%) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide09TableOfContent.tsx => TableOfContentSlide.tsx} (69%) create mode 100644 electron/servers/nextjs/app/presentation-templates/Code/TableSlide.tsx rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide07UseCaseList.tsx => TwoColumnBulletListSlide.tsx} (84%) rename electron/servers/nextjs/app/presentation-templates/Code/{CodeSlide06Workflow.tsx => WorkflowSlide.tsx} (91%) diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx b/electron/servers/nextjs/app/presentation-templates/Code/APIRequestResponseSlide.tsx similarity index 91% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/APIRequestResponseSlide.tsx index e48ebf57..2091120c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide03ApiRequestResponse.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/APIRequestResponseSlide.tsx @@ -27,7 +27,7 @@ export const Schema = z.object({ requestSnippet: z.object({ language: z.string().min(2).max(10), fileName: z.string().min(3).max(24), - content: z.string().min(20).max(220), + content: z.string().min(20).max(500), }).default({ language: "json", fileName: "request.json", @@ -59,9 +59,6 @@ export const Schema = z.object({ }).meta({ description: "Response payload example.", }), - pageLabel: z.string().min(3).max(8).optional().default("3 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -167,17 +164,6 @@ const CodeSlide03ApiRequestResponse = ({
- -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx similarity index 91% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx index 8a87b8a7..6354c620 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide04FeatureGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx @@ -88,9 +88,6 @@ export const Schema = z.object({ .meta({ description: "Six feature cards displayed in a 3x2 grid.", }), - pageLabel: z.string().min(3).max(8).optional().default("4 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -143,18 +140,6 @@ const CodeSlide04FeatureGrid = ({ data }: { data: Partial }) => {
))}
- - -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeExplanationSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeExplanationSplitSlide.tsx new file mode 100644 index 00000000..258e3057 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/CodeExplanationSplitSlide.tsx @@ -0,0 +1,264 @@ +import * as z from "zod"; + +const CODE_BLOCK_MAX_FONT_SIZE = 16; +const CODE_BLOCK_MIN_FONT_SIZE = 8; +const CODE_BLOCK_WIDTH = 506; +const CODE_BLOCK_HEIGHT = 430; +const CODE_CHAR_WIDTH_RATIO = 0.62; +const CODE_LINE_HEIGHT_RATIO = 1.25; +const CODE_FONT_FAMILY = "var(--code-font-family,'Liberation Mono', monospace)"; + +function splitCollapsedPythonImports(line: string) { + const importSegments = line + .split(/(?=\sfrom\s+[A-Za-z0-9_.]+\s+import\s+)/g) + .map((segment) => segment.trim()) + .filter(Boolean); + + return importSegments.length > 1 ? importSegments : [line]; +} + +function expandInlinePythonStatement(line: string) { + const inlineReturnMatch = line.match(/^(\s*def\s+[^(]+\([^)]*\):)\s+return\s+(.+)$/); + + if (!inlineReturnMatch) { + return [line]; + } + + return [inlineReturnMatch[1], ` return ${inlineReturnMatch[2]}`]; +} + +function expandPathListAssignment(line: string) { + const trimmedLine = line.trim(); + + if (!trimmedLine.startsWith("urlpatterns = [") || !trimmedLine.endsWith("]")) { + return [line]; + } + + const pathCalls = trimmedLine.match(/path\([^)]*\)/g); + + if (!pathCalls?.length) { + return [line]; + } + + return [ + "urlpatterns = [", + ...pathCalls.map((pathCall) => ` ${pathCall},`), + "]", + ]; +} + +function normalizePythonCode(content: string) { + const normalizedLines: string[] = []; + + for (const line of content.split("\n")) { + const importLines = splitCollapsedPythonImports(line); + + for (const importLine of importLines) { + const expandedPathLines = expandPathListAssignment(importLine); + + for (const expandedPathLine of expandedPathLines) { + normalizedLines.push(...expandInlinePythonStatement(expandedPathLine)); + } + } + } + + return normalizedLines.join("\n").replace(/\n{3,}/g, "\n\n"); +} + +function normalizeCodeContent(language?: string, content?: string) { + let normalizedContent = (content || "") + .replace(/\r\n?/g, "\n") + .replace(/\\\[/g, "[") + .replace(/\\\]/g, "]") + .trimEnd(); + + if (language?.toLowerCase() === "python") { + normalizedContent = normalizePythonCode(normalizedContent); + } + + return normalizedContent; +} + +function getCodeBlockTypography(content?: string) { + const normalizedLines = (content || "").replace(/\t/g, " ").split("\n"); + const longestLineLength = Math.max( + 1, + ...normalizedLines.map((line) => line.length) + ); + + for (let fontSize = CODE_BLOCK_MAX_FONT_SIZE; fontSize >= CODE_BLOCK_MIN_FONT_SIZE; fontSize -= 0.5) { + const lineHeight = Math.round(fontSize * CODE_LINE_HEIGHT_RATIO); + const fitsWidth = longestLineLength * fontSize * CODE_CHAR_WIDTH_RATIO <= CODE_BLOCK_WIDTH; + const fitsHeight = normalizedLines.length * lineHeight <= CODE_BLOCK_HEIGHT; + + if (fitsWidth && fitsHeight) { + return { fontSize, lineHeight }; + } + } + + return { + fontSize: CODE_BLOCK_MIN_FONT_SIZE, + lineHeight: Math.round(CODE_BLOCK_MIN_FONT_SIZE * CODE_LINE_HEIGHT_RATIO), + }; +} + +function getCodeLineRuns(content: string, lineHeight: number) { + const codeLineRuns: { text: string; marginTop: number }[] = []; + let blankLineCount = 0; + + for (const line of content.split("\n")) { + if (line.length === 0) { + blankLineCount += 1; + continue; + } + + codeLineRuns.push({ + text: line, + marginTop: blankLineCount * lineHeight, + }); + blankLineCount = 0; + } + + return codeLineRuns; +} + +export const slideLayoutId = "code-explanation-split-slide"; +export const slideLayoutName = "Code Explanation Split Slide"; +export const slideLayoutDescription = + "A two-column slide with a code panel on the left and description on the right."; + +export const Schema = z.object({ + title: z.string().min(8).max(24).default("Code + Explanation").meta({ + description: "Slide heading shown at the top-left.", + }), + codeSnippet: z.object({ + language: z.string().min(2).max(10).meta({ + description: "Programming language of the snippet", + }), + fileName: z.string().min(3).max(30).meta({ + description: "File name label shown above the code snippet.", + }), + content: z.string().min(20).max(520).meta({ + description: "The actual code content to be displayed.", + }), + }).default({ + language: "tsx", + fileName: "components/UserAuth.tsx", + content: `import { useState } from "react"; +import { login } from "@/lib/auth"; + +export function UserAuth() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const user = await login(email, password); + console.log("Logged in:", user); + }; + + return null; +} + +`, + }).meta({ + description: "Code sample shown in the left panel.", + }), + descriptionTitle: z.string().min(4).max(20).default("Description").meta({ + description: "Heading shown above the paragraph.", + }), + description: z + .string() + .min(40) + .max(360) + .default( + "This component manages credentials as local state and submits them through an async handler. The login utility abstracts network details while the handler keeps the UI flow predictable. Keep validation and side effects isolated so changes remain safe when authentication requirements evolve. " + ) + .meta({ + description: "Description paragraph shown in the right column.", + }), +}); + +export type SchemaType = z.infer; + +const CodeSlide02CodeExplanationSplit = ({ + data, +}: { + data: Partial; +}) => { + const normalizedCodeContent = normalizeCodeContent( + data.codeSnippet?.language, + data.codeSnippet?.content + ); + const codeTypography = getCodeBlockTypography(normalizedCodeContent); + const codeLineRuns = getCodeLineRuns(normalizedCodeContent, codeTypography.lineHeight); + + return ( + <> + +
+ +
+

{data.title}

+ +
+
+

+ {data.codeSnippet?.fileName} +

+
+ {codeLineRuns.map((codeLineRun, index) => ( +
+ {codeLineRun.text} +
+ ))} +
+
+ +
+

{data.descriptionTitle}

+

+ {data.description} +

+
+
+
+
+ + ); +}; + +export default CodeSlide02CodeExplanationSplit; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx deleted file mode 100644 index 9daa5dac..00000000 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide02CodeExplanationSplit.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as z from "zod"; - - -export const slideLayoutId = "code-explanation-split-slide"; -export const slideLayoutName = "Code Explanation Split Slide"; -export const slideLayoutDescription = - "A two-column slide with a code panel on the left and description on the right."; - -export const Schema = z.object({ - title: z.string().min(8).max(24).default("Code + Explanation").meta({ - description: "Slide heading shown at the top-left.", - }), - codeSnippet: z.object({ - language: z.string().min(2).max(10).meta({ - description: "Programming language of the snippet", - }), - fileName: z.string().min(3).max(30).meta({ - description: "File name label shown above the code snippet.", - }), - content: z.string().min(20).max(520).meta({ - description: "The actual code content to be displayed.", - }), - }).default({ - language: "tsx", - fileName: "components/UserAuth.tsx", - content: `import { useState } from "react"; -import { login } from "@/lib/auth"; - -export function UserAuth() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - const user = await login(email, password); - console.log("Logged in:", user); - }; - - return null; -} - -`, - }).meta({ - description: "Code sample shown in the left panel.", - }), - descriptionTitle: z.string().min(4).max(20).default("Description").meta({ - description: "Heading shown above the paragraph.", - }), - description: z - .string() - .min(40) - .max(360) - .default( - "This component manages credentials as local state and submits them through an async handler. The login utility abstracts network details while the handler keeps the UI flow predictable. Keep validation and side effects isolated so changes remain safe when authentication requirements evolve. " - ) - .meta({ - description: "Description paragraph shown in the right column.", - }), - pageLabel: z.string().min(3).max(8).optional().default("2 / 11").meta({ - description: "Bottom pagination label.", - }), -}); - -export type SchemaType = z.infer; - -const CodeSlide02CodeExplanationSplit = ({ - data, -}: { - data: Partial; -}) => { - - return ( - <> - -
- -
-

{data.title}

- -
-
-

- {data.codeSnippet?.fileName} -

-
-
-                
-                  {data.codeSnippet?.content}
-                
-              
-
- -
-

{data.descriptionTitle}

-

- {data.description} -

-
-
-
- -
- {data.pageLabel} -
-
- - ); -}; - -export default CodeSlide02CodeExplanationSplit; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx deleted file mode 100644 index 1f55acec..00000000 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide05ComparisonTable.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import * as z from "zod"; - -const ComparisonRowSchema = z.object({ - feature: z.string().min(4).max(17).meta({ - description: "Feature label shown in the first column.", - }), - column1: z.string().max(10).meta({ - description: "Column 1 cell value.", - }), - column2: z.string().max(10).meta({ - description: "Column 2 cell value.", - }), - column3: z.string().max(10).meta({ - description: "Column 3 cell value.", - }), -}); - -export const slideLayoutId = "table-slide"; -export const slideLayoutName = "Table Slide"; -export const slideLayoutDescription = - "A slide with title and a table."; - -export const Schema = z.object({ - title: z.string().min(6).max(18).default("Comparison").meta({ - description: "Slide title shown above the table.", - }), - tableColumns: z.array(z.string().max(4)).meta({ - description: "Table columns shown in the first row.", - }).default(["Feature", "Column 1", "Column 2", "Column 3"]), - rows: z - .array(ComparisonRowSchema) - .min(1) - .max(6) - .default([ - { feature: "Component-based", column1: "check", column2: "check", column3: "check" }, - { feature: "TypeScript Support", column1: "check", column2: "check", column3: "check" }, - { feature: "Learning Curve", column1: "Medium", column2: "Easy", column3: "Steep" }, - { feature: "Bundle Size", column1: "40KB", column2: "34KB", column3: "167KB" }, - { feature: "Performance", column1: "Excellent", column2: "Excellent", column3: "Good" }, - { feature: "Community Size", column1: "Huge", column2: "Large", column3: "Large" }, - ]) - .meta({ - description: "Six comparison rows shown in the table.", - }), - pageLabel: z.string().min(3).max(8).optional().default("5 / 11").meta({ - description: "Bottom pagination label.", - }), -}); - -export type SchemaType = z.infer; - -function renderCell(value: string) { - if (value && value.toLowerCase() === "check") { - return ; - } - - return {value}; -} - -const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) => { - - return ( - <> - -
- -

{data.title}

- -
-
- - {data?.tableColumns?.map((column) => ( -

- {column} -

- ))} -
- -
- {data?.rows?.map((row) => ( -
-

- {row.feature} -

-
{renderCell(row.column1)}
-
{renderCell(row.column2)}
-
{renderCell(row.column3)}
-
- ))} -
- -
- -
- {data.pageLabel} -
-
- - ); -}; - -export default CodeSlide05ComparisonTable; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CoverSlide.tsx similarity index 81% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/CoverSlide.tsx index 36de395f..367c3e39 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide01RoadmapCover.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CoverSlide.tsx @@ -22,9 +22,6 @@ export const Schema = z.object({ .meta({ description: "Supporting subtitle shown under the heading.", }), - pageLabel: z.string().min(3).max(8).optional().default("1 / 11").meta({ - description: "", - }), }); export type SchemaType = z.infer; @@ -47,17 +44,6 @@ const CodeSlide01RoadmapCover = ({ data }: { data: Partial }) => {

{data.subtitle}

- -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx b/electron/servers/nextjs/app/presentation-templates/Code/DescriptionAndMetricsSlide.tsx similarity index 82% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/DescriptionAndMetricsSlide.tsx index d13c0fdd..cbf1fbaf 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide10MetricsSplit.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/DescriptionAndMetricsSlide.tsx @@ -41,14 +41,11 @@ export const Schema = z.object({ { value: "50k+", label: "Active Users", subtext: "Last 12 months" }, { value: "50k+", label: "Active Users", subtext: "Last 12 months" }, { value: "50k+", label: "Active Users", subtext: "Last 12 months" }, - { value: "50k+", label: "Active Users", subtext: "Last 12 months" } + ]) .meta({ description: "Metric cards shown in the right column.", }), - pageLabel: z.string().min(3).max(8).optional().default("10 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -70,17 +67,17 @@ const CodeSlide10MetricsSplit = ({ data }: { data: Partial }) => {

{data.title}

-
+

{data.explanationTitle}

{data.explanation}

-
+
{data?.metrics?.map((metric, index) => (
}) => { ))}
- -
- {data?.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx b/electron/servers/nextjs/app/presentation-templates/Code/DescriptionTextSlide.tsx similarity index 79% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/DescriptionTextSlide.tsx index 0211fa9c..a897992c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide08CodeExplanationText.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/DescriptionTextSlide.tsx @@ -22,9 +22,6 @@ export const Schema = z.object({ .meta({ description: "Long-form explanation body.", }), - pageLabel: z.string().min(3).max(8).optional().default("8 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -48,17 +45,6 @@ const CodeSlide08CodeExplanationText = ({ data }: { data: Partial })

{data.descriptionTitle}

{data.description}

- -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx b/electron/servers/nextjs/app/presentation-templates/Code/MetricsGridSlide.tsx similarity index 85% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/MetricsGridSlide.tsx index 3a2f9f51..6218e233 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide11MetricsGrid.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/MetricsGridSlide.tsx @@ -36,9 +36,6 @@ export const Schema = z.object({ .meta({ description: "Metrics cards in a grid.", }), - pageLabel: z.string().min(3).max(8).optional().default("11 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -78,18 +75,6 @@ const CodeSlide11MetricsGrid = ({ data }: { data: Partial }) => {
))}

- - -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx b/electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx similarity index 69% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx index c697c6c7..38514b2d 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide09TableOfContent.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx @@ -34,10 +34,6 @@ export const Schema = z.object({ .meta({ description: "Table of contents entries.", }), - - pageLabel: z.string().min(3).max(8).optional().default("9 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -49,13 +45,14 @@ function TocColumn({ items }: { items: { number: string; label: string, descript return ( -
-
- -
+
+
+
+ +
+

{item.number}

+
- -

{item.number}

{item.label}

{item.description &&

{item.description}

}
@@ -91,17 +88,6 @@ const CodeSlide09TableOfContent = ({ data }: { data: Partial }) => {
- -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/TableSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Code/TableSlide.tsx new file mode 100644 index 00000000..579feac6 --- /dev/null +++ b/electron/servers/nextjs/app/presentation-templates/Code/TableSlide.tsx @@ -0,0 +1,160 @@ +import * as z from "zod"; + +const DEFAULT_TABLE_COLUMNS = ["Feature", "Column 1", "Column 2", "Column 3"]; + +const DEFAULT_ROWS = [ + { cells: ["Component-based", "check", "check", "check"] }, + { cells: ["TypeScript Support", "check", "check", "check"] }, + { cells: ["Learning Curve", "Medium", "Easy", "Steep"] }, + { cells: ["Bundle Size", "40KB", "34KB", "167KB"] }, + { cells: ["Performance", "Excellent", "Excellent", "Good"] }, + { cells: ["Community Size", "Huge", "Large", "Large"] }, +]; + +const ComparisonRowSchema = z.object({ + cells: z.array(z.string().max(24)).min(1).max(6).meta({ + description: "Cell values for this row in left-to-right order. Match the number of table columns.", + }), +}); + +export const slideLayoutId = "table-slide"; +export const slideLayoutName = "Table Slide"; +export const slideLayoutDescription = + "A slide with title and a table."; + +export const Schema = z.object({ + title: z.string().min(6).max(18).default("Comparison").meta({ + description: "Slide title shown above the table.", + }), + tableColumns: z.array(z.string().max(18)).min(1).max(6).meta({ + description: "Table columns shown in the first row.", + }).default(DEFAULT_TABLE_COLUMNS), + rows: z + .array(ComparisonRowSchema) + .min(1) + .max(6) + .default(DEFAULT_ROWS) + .meta({ + description: "Table rows where each row contains a cells array matching the table columns.", + }), +}).superRefine((value, ctx) => { + value.rows.forEach((row, rowIndex) => { + if (row.cells.length !== value.tableColumns.length) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["rows", rowIndex, "cells"], + message: "Each row must contain the same number of cells as tableColumns.", + }); + } + }); +}); + +export type SchemaType = z.infer; + +function getGridTemplateColumns(columnCount: number) { + if (columnCount <= 1) { + return "minmax(0, 1fr)"; + } + + return `minmax(0, 1.4fr) repeat(${columnCount - 1}, minmax(0, 1fr))`; +} + +function renderCell(value: string, isFirstColumn: boolean) { + if (!isFirstColumn && value && value.toLowerCase() === "check") { + return ; + } + + return ( + + {value} + + ); +} + +const CodeSlide05ComparisonTable = ({ data }: { data: Partial }) => { + const tableColumns = data.tableColumns?.length ? data.tableColumns : DEFAULT_TABLE_COLUMNS; + const rows = data.rows?.length ? data.rows : DEFAULT_ROWS; + const gridTemplateColumns = getGridTemplateColumns(tableColumns.length); + + return ( + <> + +
+ +

{data.title}

+ +
+
+ + {tableColumns.map((column, columnIndex) => ( +

+ {column} +

+ ))} +
+ +
+ {rows.map((row, rowIndex) => ( +
+ {row.cells.map((cell, cellIndex) => ( +
+ {renderCell(cell, cellIndex === 0)} +
+ ))} +
+ ))} +
+ +
+
+ + ); +}; + +export default CodeSlide05ComparisonTable; diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx b/electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx similarity index 84% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx index d08f432f..0974177c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide07UseCaseList.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx @@ -26,9 +26,6 @@ export const Schema = z.object({ .meta({ description: "Eight use-case items shown in two columns.", }), - pageLabel: z.string().min(3).max(8).optional().default("7 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -74,18 +71,6 @@ const CodeSlide07UseCaseList = ({ data }: { data: Partial }) => {
))}
- - -
- {data.pageLabel} -
); diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx b/electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx similarity index 91% rename from electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx rename to electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx index d64d4071..7f1507d8 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CodeSlide06Workflow.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx @@ -71,9 +71,6 @@ export const Schema = z.object({ .meta({ description: "Workflow steps shown in sequence.", }), - pageLabel: z.string().min(3).max(8).optional().default("6 / 11").meta({ - description: "Bottom pagination label.", - }), }); export type SchemaType = z.infer; @@ -130,17 +127,6 @@ const CodeSlide06Workflow = ({ data }: { data: Partial }) => { ))}
- -
- {data.pageLabel} -

); diff --git a/electron/servers/nextjs/app/presentation-templates/index.tsx b/electron/servers/nextjs/app/presentation-templates/index.tsx index 411255a9..aaa1b3d7 100644 --- a/electron/servers/nextjs/app/presentation-templates/index.tsx +++ b/electron/servers/nextjs/app/presentation-templates/index.tsx @@ -4,17 +4,17 @@ import { TemplateWithData, TemplateGroupSettings, createTemplateEntry, TemplateL // TODO: Step 1: Import All templates Layouts Here (like the ones below) // Code templates -import CodeSlide01RoadmapCover, { Schema as CodeRoadmapCoverSchema, slideLayoutId as CodeRoadmapCoverId, slideLayoutName as CodeRoadmapCoverName, slideLayoutDescription as CodeRoadmapCoverDesc } from "./Code/CodeSlide01RoadmapCover"; -import CodeSlide02CodeExplanationSplit, { Schema as CodeExplanationSplitSchema, slideLayoutId as CodeExplanationSplitId, slideLayoutName as CodeExplanationSplitName, slideLayoutDescription as CodeExplanationSplitDesc } from "./Code/CodeSlide02CodeExplanationSplit"; -import CodeSlide03ApiRequestResponse, { Schema as CodeApiRequestResponseSchema, slideLayoutId as CodeApiRequestResponseId, slideLayoutName as CodeApiRequestResponseName, slideLayoutDescription as CodeApiRequestResponseDesc } from "./Code/CodeSlide03ApiRequestResponse"; -import CodeSlide04FeatureGrid, { Schema as CodeFeatureGridSchema, slideLayoutId as CodeFeatureGridId, slideLayoutName as CodeFeatureGridName, slideLayoutDescription as CodeFeatureGridDesc } from "./Code/CodeSlide04FeatureGrid"; -import CodeSlide05ComparisonTable, { Schema as CodeComparisonTableSchema, slideLayoutId as CodeComparisonTableId, slideLayoutName as CodeComparisonTableName, slideLayoutDescription as CodeComparisonTableDesc } from "./Code/CodeSlide05ComparisonTable"; -import CodeSlide06Workflow, { Schema as CodeWorkflowSchema, slideLayoutId as CodeWorkflowId, slideLayoutName as CodeWorkflowName, slideLayoutDescription as CodeWorkflowDesc } from "./Code/CodeSlide06Workflow"; -import CodeSlide07UseCaseList, { Schema as CodeUseCaseListSchema, slideLayoutId as CodeUseCaseListId, slideLayoutName as CodeUseCaseListName, slideLayoutDescription as CodeUseCaseListDesc } from "./Code/CodeSlide07UseCaseList"; -import CodeSlide08CodeExplanationText, { Schema as CodeExplanationTextSchema, slideLayoutId as CodeExplanationTextId, slideLayoutName as CodeExplanationTextName, slideLayoutDescription as CodeExplanationTextDesc } from "./Code/CodeSlide08CodeExplanationText"; -import CodeSlide09TableOfContent, { Schema as CodeTableOfContentSchema, slideLayoutId as CodeTableOfContentId, slideLayoutName as CodeTableOfContentName, slideLayoutDescription as CodeTableOfContentDesc } from "./Code/CodeSlide09TableOfContent"; -import CodeSlide10MetricsSplit, { Schema as CodeMetricsSplitSchema, slideLayoutId as CodeMetricsSplitId, slideLayoutName as CodeMetricsSplitName, slideLayoutDescription as CodeMetricsSplitDesc } from "./Code/CodeSlide10MetricsSplit"; -import CodeSlide11MetricsGrid, { Schema as CodeMetricsGridSchema, slideLayoutId as CodeMetricsGridId, slideLayoutName as CodeMetricsGridName, slideLayoutDescription as CodeMetricsGridDesc } from "./Code/CodeSlide11MetricsGrid"; +import CodeSlide01RoadmapCover, { Schema as CodeRoadmapCoverSchema, slideLayoutId as CodeRoadmapCoverId, slideLayoutName as CodeRoadmapCoverName, slideLayoutDescription as CodeRoadmapCoverDesc } from "./Code/CoverSlide"; +import CodeSlide02CodeExplanationSplit, { Schema as CodeExplanationSplitSchema, slideLayoutId as CodeExplanationSplitId, slideLayoutName as CodeExplanationSplitName, slideLayoutDescription as CodeExplanationSplitDesc } from "./Code/CodeExplanationSplitSlide"; +import CodeSlide03ApiRequestResponse, { Schema as CodeApiRequestResponseSchema, slideLayoutId as CodeApiRequestResponseId, slideLayoutName as CodeApiRequestResponseName, slideLayoutDescription as CodeApiRequestResponseDesc } from "./Code/APIRequestResponseSlide"; +import CodeSlide04FeatureGrid, { Schema as CodeFeatureGridSchema, slideLayoutId as CodeFeatureGridId, slideLayoutName as CodeFeatureGridName, slideLayoutDescription as CodeFeatureGridDesc } from "./Code/CardsGridSlide"; +import CodeSlide05ComparisonTable, { Schema as CodeComparisonTableSchema, slideLayoutId as CodeComparisonTableId, slideLayoutName as CodeComparisonTableName, slideLayoutDescription as CodeComparisonTableDesc } from "./Code/TableSlide"; +import CodeSlide06Workflow, { Schema as CodeWorkflowSchema, slideLayoutId as CodeWorkflowId, slideLayoutName as CodeWorkflowName, slideLayoutDescription as CodeWorkflowDesc } from "./Code/WorkflowSlide"; +import CodeSlide07UseCaseList, { Schema as CodeUseCaseListSchema, slideLayoutId as CodeUseCaseListId, slideLayoutName as CodeUseCaseListName, slideLayoutDescription as CodeUseCaseListDesc } from "./Code/TwoColumnBulletListSlide"; +import CodeSlide08CodeExplanationText, { Schema as CodeExplanationTextSchema, slideLayoutId as CodeExplanationTextId, slideLayoutName as CodeExplanationTextName, slideLayoutDescription as CodeExplanationTextDesc } from "./Code/DescriptionTextSlide"; +import CodeSlide09TableOfContent, { Schema as CodeTableOfContentSchema, slideLayoutId as CodeTableOfContentId, slideLayoutName as CodeTableOfContentName, slideLayoutDescription as CodeTableOfContentDesc } from "./Code/TableOfContentSlide"; +import CodeSlide10MetricsSplit, { Schema as CodeMetricsSplitSchema, slideLayoutId as CodeMetricsSplitId, slideLayoutName as CodeMetricsSplitName, slideLayoutDescription as CodeMetricsSplitDesc } from "./Code/DescriptionAndMetricsSlide"; +import CodeSlide11MetricsGrid, { Schema as CodeMetricsGridSchema, slideLayoutId as CodeMetricsGridId, slideLayoutName as CodeMetricsGridName, slideLayoutDescription as CodeMetricsGridDesc } from "./Code/MetricsGridSlide"; // Education templates import EducationCoverSlide, { Schema as EduCoverSchema, slideLayoutId as EduCoverId, slideLayoutName as EduCoverName, slideLayoutDescription as EduCoverDesc } from "./Education/EducationCoverSlide"; @@ -244,17 +244,17 @@ import reportSettings from "./Report/settings.json"; // TODO: Step 3: Create template entries for each template (like the ones below) export const codeTemplates: TemplateWithData[] = [ - createTemplateEntry(CodeSlide01RoadmapCover, CodeRoadmapCoverSchema, CodeRoadmapCoverId, CodeRoadmapCoverName, CodeRoadmapCoverDesc, "code", "CodeSlide01RoadmapCover"), - createTemplateEntry(CodeSlide02CodeExplanationSplit, CodeExplanationSplitSchema, CodeExplanationSplitId, CodeExplanationSplitName, CodeExplanationSplitDesc, "code", "CodeSlide02CodeExplanationSplit"), - createTemplateEntry(CodeSlide03ApiRequestResponse, CodeApiRequestResponseSchema, CodeApiRequestResponseId, CodeApiRequestResponseName, CodeApiRequestResponseDesc, "code", "CodeSlide03ApiRequestResponse"), - createTemplateEntry(CodeSlide04FeatureGrid, CodeFeatureGridSchema, CodeFeatureGridId, CodeFeatureGridName, CodeFeatureGridDesc, "code", "CodeSlide04FeatureGrid"), - createTemplateEntry(CodeSlide05ComparisonTable, CodeComparisonTableSchema, CodeComparisonTableId, CodeComparisonTableName, CodeComparisonTableDesc, "code", "CodeSlide05ComparisonTable"), - createTemplateEntry(CodeSlide06Workflow, CodeWorkflowSchema, CodeWorkflowId, CodeWorkflowName, CodeWorkflowDesc, "code", "CodeSlide06Workflow"), - createTemplateEntry(CodeSlide07UseCaseList, CodeUseCaseListSchema, CodeUseCaseListId, CodeUseCaseListName, CodeUseCaseListDesc, "code", "CodeSlide07UseCaseList"), - createTemplateEntry(CodeSlide08CodeExplanationText, CodeExplanationTextSchema, CodeExplanationTextId, CodeExplanationTextName, CodeExplanationTextDesc, "code", "CodeSlide08CodeExplanationText"), - createTemplateEntry(CodeSlide09TableOfContent, CodeTableOfContentSchema, CodeTableOfContentId, CodeTableOfContentName, CodeTableOfContentDesc, "code", "CodeSlide09TableOfContent"), - createTemplateEntry(CodeSlide10MetricsSplit, CodeMetricsSplitSchema, CodeMetricsSplitId, CodeMetricsSplitName, CodeMetricsSplitDesc, "code", "CodeSlide10MetricsSplit"), - createTemplateEntry(CodeSlide11MetricsGrid, CodeMetricsGridSchema, CodeMetricsGridId, CodeMetricsGridName, CodeMetricsGridDesc, "code", "CodeSlide11MetricsGrid"), + createTemplateEntry(CodeSlide01RoadmapCover, CodeRoadmapCoverSchema, CodeRoadmapCoverId, CodeRoadmapCoverName, CodeRoadmapCoverDesc, "code", "CoverSlide"), + createTemplateEntry(CodeSlide02CodeExplanationSplit, CodeExplanationSplitSchema, CodeExplanationSplitId, CodeExplanationSplitName, CodeExplanationSplitDesc, "code", "CodeExplanationSplitSlide"), + createTemplateEntry(CodeSlide03ApiRequestResponse, CodeApiRequestResponseSchema, CodeApiRequestResponseId, CodeApiRequestResponseName, CodeApiRequestResponseDesc, "code", "APIRequestResponseSlide"), + createTemplateEntry(CodeSlide04FeatureGrid, CodeFeatureGridSchema, CodeFeatureGridId, CodeFeatureGridName, CodeFeatureGridDesc, "code", "CardsGridSlide"), + createTemplateEntry(CodeSlide05ComparisonTable, CodeComparisonTableSchema, CodeComparisonTableId, CodeComparisonTableName, CodeComparisonTableDesc, "code", "TableSlide"), + createTemplateEntry(CodeSlide06Workflow, CodeWorkflowSchema, CodeWorkflowId, CodeWorkflowName, CodeWorkflowDesc, "code", "WorkflowSlide"), + createTemplateEntry(CodeSlide07UseCaseList, CodeUseCaseListSchema, CodeUseCaseListId, CodeUseCaseListName, CodeUseCaseListDesc, "code", "TwoColumnBulletListSlide"), + createTemplateEntry(CodeSlide08CodeExplanationText, CodeExplanationTextSchema, CodeExplanationTextId, CodeExplanationTextName, CodeExplanationTextDesc, "code", "DescriptionTextSlide"), + createTemplateEntry(CodeSlide09TableOfContent, CodeTableOfContentSchema, CodeTableOfContentId, CodeTableOfContentName, CodeTableOfContentDesc, "code", "TableOfContentSlide"), + createTemplateEntry(CodeSlide10MetricsSplit, CodeMetricsSplitSchema, CodeMetricsSplitId, CodeMetricsSplitName, CodeMetricsSplitDesc, "code", "DescriptionAndMetricsSlide"), + createTemplateEntry(CodeSlide11MetricsGrid, CodeMetricsGridSchema, CodeMetricsGridId, CodeMetricsGridName, CodeMetricsGridDesc, "code", "MetricsGridSlide"), ]; export const educationTemplates: TemplateWithData[] = [ From c1908635df0e9607f5da5770149849d3b49ae4eb Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Thu, 9 Apr 2026 23:37:14 +0545 Subject: [PATCH 17/18] feat: new tempalte improvements --- .../BusinessChallengesGridSlide.tsx | 2 +- .../ProductOverview/ComparisonChartSlide.tsx | 8 +-- .../ProductOverview/ImageGallerySlide.tsx | 2 +- .../ProductOverview/MissionVisionSlide.tsx | 4 +- .../ProductOverview/OurServicesSlide.tsx | 8 +-- .../app/presentation-templates/index.tsx | 58 ++++++++++--------- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx index 3426444a..56e8e510 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/BusinessChallengesGridSlide.tsx @@ -9,7 +9,7 @@ const BlockSchema = z.object({ heading: z.string().max(30).meta({ description: "Short heading for a single block of text.", }), - body: z.string().max(100).meta({ + body: z.string().max(80).meta({ description: "Description text for a single block of text.", }), }); diff --git a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx index 514c1e58..5d1f5e59 100644 --- a/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/ProductOverview/ComparisonChartSlide.tsx @@ -122,7 +122,7 @@ function StatusIcon({ return {checkIconAlt}; } - return

{status}

+ return

{status}

} const ComparisonChartSlide = ({ data }: { data: Partial }) => { @@ -135,14 +135,14 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { checkIcon, crossIcon, } = data; - const safeColumns = columns && columns.length > 0 ? columns : DEFAULT_COLUMNS; + const safeColumns = columns && columns.length > 0 ? columns : []; const resolvedHighlightedColumnIndex = highlightedColumnIndex && highlightedColumnIndex >= 1 && highlightedColumnIndex <= safeColumns.length ? highlightedColumnIndex : Math.min(4, safeColumns.length); - const safeRows = rows && rows.length > 0 ? rows : DEFAULT_ROWS; + const safeRows = rows && rows.length > 0 ? rows : []; const normalizedRows = safeRows.map((row) => { const rowCells = "cells" in row @@ -195,7 +195,7 @@ const ComparisonChartSlide = ({ data }: { data: Partial }) => { }} >
- {safeColumns.map((column, index) => ( + {safeColumns.map((column: any, index: any) => (
}) => { {bottomWideImage?.__image_prompt__} }) => { }} >

}) => { {card.heading}

Date: Fri, 10 Apr 2026 00:11:39 +0545 Subject: [PATCH 18/18] feat: new templates fonts & icons color changing --- electron/servers/nextjs/app/globals.css | 31 --- .../Code/CardsGridSlide.tsx | 10 +- .../Code/TableOfContentSlide.tsx | 8 +- .../Code/TwoColumnBulletListSlide.tsx | 2 +- .../Code/WorkflowSlide.tsx | 12 +- .../Education/EducationAboutSlide.tsx | 4 +- .../Education/EducationChartPrimitives.tsx | 83 +------- .../Education/EducationContentSplitSlide.tsx | 76 +++---- .../Education/EducationCoverSlide.tsx | 56 +++--- .../Education/EducationImageGallerySlide.tsx | 90 +++++---- .../Education/EducationReportChartSlide.tsx | 78 ++++---- .../Education/EducationServicesSplitSlide.tsx | 100 ++++----- .../EducationStatisticsGridSlide.tsx | 164 +++++++-------- .../EducationTableOfContentsSlide.tsx | 56 +++--- .../Education/EducationTimelineSlide.tsx | 41 ++-- ...ulletListWithTitleDescriptionIconSlide.tsx | 142 +++++++------ ...ulletListWithIconTitleDescriptionSlide.tsx | 107 +++++----- .../Report/DataAnalysisDashboardSlide.tsx | 167 ++++++++-------- ...ntalHeightSpanningImagesWithTitleSlide.tsx | 77 +++---- .../Report/IntroCoverSlide.tsx | 79 ++++---- .../Report/MetricsSlide.tsx | 83 ++++---- .../Report/MilestoneSlide.tsx | 115 +++++------ .../TitleChartWithMetricsCardsSlide.tsx | 121 +++++------ .../Report/TitleDescriptionChartSlide.tsx | 122 ++++++----- .../Report/TitleDescriptionImageSlide.tsx | 95 ++++----- .../Report/TitleImageBulletCardsSlide.tsx | 99 ++++----- .../Report/TitleMetricsSlide.tsx | 60 +++--- ...TitleWorkflowWithTitleDescriptionSlide.tsx | 189 +++++++++--------- 28 files changed, 1143 insertions(+), 1124 deletions(-) diff --git a/electron/servers/nextjs/app/globals.css b/electron/servers/nextjs/app/globals.css index aa9e7d95..9674bec6 100644 --- a/electron/servers/nextjs/app/globals.css +++ b/electron/servers/nextjs/app/globals.css @@ -82,37 +82,6 @@ strong { } -@font-face { - font-family: 'Times New Roman'; - src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/times.ttf') format('truetype'); - font-style: normal; - font-display: swap; - -} - -@font-face { - font-family: 'Helvetica Neue'; - src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeue-Bold.otf') format('truetype'); - font-weight: 600; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Helvetica Neue'; - src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeue-Medium.otf') format('truetype'); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Helvetica Neue'; - src: url('https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/fonts/HelveticaNeue-Roman.otf') format('truetype'); - font-weight: 400; - font-style: normal; - font-display: swap; -} diff --git a/electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx index 6354c620..683afe62 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/CardsGridSlide.tsx @@ -1,3 +1,4 @@ +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; import * as z from "zod"; const FeatureCardSchema = z.object({ @@ -129,10 +130,17 @@ const CodeSlide04FeatureGrid = ({ data }: { data: Partial }) => { backgroundColor: "var(--primary-color,#2B7FFF33)", }} > - {feature.icon.__icon_query__} */} +

diff --git a/electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx index 38514b2d..a5cc7539 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/TableOfContentSlide.tsx @@ -11,9 +11,9 @@ export const Schema = z.object({ }), items: z .array(z.object({ - number: z.string().min(2).max(2).meta({"description": "Bullet Serial Number"}), - label: z.string().min(3).max(30).meta({"description": "Page/Content Name"}), - description: z.string().min(3).max(100).optional().meta({"description": "Short description for the content section."}), + number: z.string().min(2).max(2).meta({ "description": "Bullet Serial Number" }), + label: z.string().min(3).max(30).meta({ "description": "Page/Content Name" }), + description: z.string().min(3).max(100).optional().meta({ "description": "Short description for the content section." }), })) .min(12) .max(12) @@ -50,7 +50,7 @@ function TocColumn({ items }: { items: { number: string; label: string, descript
-

{item.number}

+

{item.number}

{item.label}

diff --git a/electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx index 0974177c..cfdab86f 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/TwoColumnBulletListSlide.tsx @@ -62,7 +62,7 @@ const CodeSlide07UseCaseList = ({ data }: { data: Partial }) => { style={{ borderColor: "var(--primary-color,#2B7FFF4D)", backgroundColor: "var(--primary-color,#2B7FFF33)", - color: "var(--primary-color,#51A2FF)", + color: "var(--primary-text,#51A2FF)", }} > {index + 1} diff --git a/electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx index 7f1507d8..a9e45f54 100644 --- a/electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Code/WorkflowSlide.tsx @@ -1,3 +1,4 @@ +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; import { Fragment } from "react"; import * as z from "zod"; @@ -108,11 +109,18 @@ const CodeSlide06Workflow = ({ data }: { data: Partial }) => { backgroundColor: "var(--primary-color,#2B7FFF33)", }} > - {step.icon.__icon_query__} + {/* {step.icon.__icon_query__} + /> */}

{step.title}

{step.description}

diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx index 72402f2c..5e2434a9 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationAboutSlide.tsx @@ -54,13 +54,13 @@ const EducationAboutSlide = ({ data }: { data: Partial }) => { return (<> - +
diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx index 151077c6..e0921663 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationChartPrimitives.tsx @@ -69,19 +69,8 @@ type TooltipPayloadItem = { value?: string | number; }; -type TooltipProps = { - active?: boolean; - label?: string | number; - payload?: TooltipPayloadItem[]; -}; -type PieLabelProps = { - name?: string; - percent?: number; - textAnchor?: "start" | "middle" | "end"; - x?: number; - y?: number; -}; + const DEFAULT_COLORS = [ "var(--graph-0,#4A15A8)", @@ -113,21 +102,7 @@ function isSimpleDatum(item: EducationChartDatum): item is SimpleDatum { return typeof (item as SimpleDatum).name === "string" && typeof (item as SimpleDatum).value === "number"; } -function isMultiSeriesDatum(item: EducationChartDatum): item is MultiSeriesDatum { - return ( - typeof (item as MultiSeriesDatum).name === "string" && - typeof (item as MultiSeriesDatum).values === "object" && - (item as MultiSeriesDatum).values !== null - ); -} -function isDivergingDatum(item: EducationChartDatum): item is DivergingDatum { - return ( - typeof (item as DivergingDatum).name === "string" && - typeof (item as DivergingDatum).positive === "number" && - typeof (item as DivergingDatum).negative === "number" - ); -} function isScatterDatum(item: EducationChartDatum): item is ScatterDatum { return typeof (item as ScatterDatum).x === "number" && typeof (item as ScatterDatum).y === "number"; @@ -215,70 +190,14 @@ const renderPieInsideLabel = (props: any) => { ); }; -function transformMultiSeriesData(data: EducationChartDatum[], series: string[]) { - return data - .filter(isMultiSeriesDatum) - .map((item) => { - const transformed: Record = { - name: item.name, - }; - series.forEach((serie) => { - transformed[serie] = item.values?.[serie] ?? 0; - }); - return transformed; - }); -} -function transformDivergingData(data: EducationChartDatum[]) { - return data - .filter(isDivergingDatum) - .map((item) => ({ - name: item.name, - positive: item.positive, - negative: -Math.abs(item.negative), - })); -} - -function CustomTooltip({ active, payload, label }: TooltipProps) { - if (!active || !payload || payload.length === 0) { - return null; - } - - return ( -
-

{label}

- {payload.map((entry, index) => ( -

- {entry.name ?? entry.dataKey}: {String(entry.value)} -

- ))} -
- ); -} function getChartColor(index: number) { return DEFAULT_COLORS[index % DEFAULT_COLORS.length]; } -function renderPieLabel({ name, percent = 0, x = 0, y = 0, textAnchor = "middle" }: PieLabelProps) { - if (percent < 0.08) { - return null; - } - - return ( - - {`${name ?? ""} ${(percent * 100).toFixed(0)}%`} - - ); -} function ChartLegend({ showLegend }: { showLegend: boolean }) { if (!showLegend) { diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx index 8be4223e..015ef894 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationContentSplitSlide.tsx @@ -47,45 +47,49 @@ const EducationContentSplitSlide = ({ data }: { data: Partial }) => const { heading, tagline, body, images } = data; return ( -
-
-
-
- {images?.[0]?.__image_prompt__} -
-
-
{images?.[1]?.__image_prompt__}
-
{images?.[2]?.__image_prompt__}
-
-
+ <> -
-

{heading}

-

- {tagline} -

-

{body}

+ +
+
+
+
+ {images?.[0]?.__image_prompt__} +
+
+
{images?.[1]?.__image_prompt__}
+
{images?.[2]?.__image_prompt__}
+
+
+ +
+

{heading}

+

+ {tagline} +

+

{body}

+
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx index f1bdcc1f..88e1c4f8 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationCoverSlide.tsx @@ -31,38 +31,42 @@ const EducationCoverSlide = ({ data }: { data: Partial }) => { const { name, title, backgroundImage } = data; return ( -
- {backgroundImage?.__image_prompt__} + <> +
- - -
- {name &&

{name}

- }

- {title} -

+ {backgroundImage?.__image_prompt__} + +
+ + +
+ {name &&

{name}

+ }

+ {title} +

+
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx index 2423dfe7..e1f6bd38 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationImageGallerySlide.tsx @@ -33,52 +33,56 @@ const EducationImageGallerySlide = ({ data }: { data: Partial }) => const { title, body, galleryImages } = data; return ( -
-
-
- {galleryImages?.[0].__image_prompt__} - {galleryImages?.[1].__image_prompt__} - {galleryImages?.[2].__image_prompt__} - {galleryImages?.[3].__image_prompt__} - {galleryImages?.[4].__image_prompt__} -
+ <> -
-

- {title} -

-

- {body} -

+ +
+
+
+ {galleryImages?.[0].__image_prompt__} + {galleryImages?.[1].__image_prompt__} + {galleryImages?.[2].__image_prompt__} + {galleryImages?.[3].__image_prompt__} + {galleryImages?.[4].__image_prompt__} +
+ +
+

+ {title} +

+

+ {body} +

+
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx index 51532365..b8387f3a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationReportChartSlide.tsx @@ -136,50 +136,54 @@ const EducationReportChartSlide = ({ data }: { data: Partial }) => { const chartHeightClass = slideData.showStatusMessage ? "h-[372px]" : "h-[486px]"; return ( -
-
-
-
-

- {slideData.title} -

-

- {slideData.body} + <> + + +

+
+
+
+

+ {slideData.title} +

+

+ {slideData.body} +

+
+ +

+ {slideData.footnote}

-

- {slideData.footnote} -

-
+
+

+ {slideData.chartTitle} +

+

+ {slideData.dateRange} +

-
-

- {slideData.chartTitle} -

-

- {slideData.dateRange} -

- -
- +
+ +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx index 634a4ac4..fa3b9323 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationServicesSplitSlide.tsx @@ -91,64 +91,68 @@ const EducationServicesSplitSlide = ({ data }: { data: Partial }) => return ( -
-
-
-

- {title} -

-
+ <> + + +
+
+
+

+ {title} +

+
-
- {sections?.map((section, index) => ( -
-
+ {sections?.map((section, index) => ( +
+
- {section.image?.__image_prompt__} + {section.image?.__image_prompt__} +
+
+

{section.heading}

+

+ {section.tagline} +

+

+ {section.body} +

+
-
-

{section.heading}

-

- {section.tagline} -

-

- {section.body} -

-
-
- ))} + ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx index 46ac18a1..3a76a0e5 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationStatisticsGridSlide.tsx @@ -51,89 +51,93 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial }) = return ( -
-
-
-
-

- {data.title} -

-

- {data.description} -

+ <> + + +
+
+
+
+

+ {data.title} +

+

+ {data.description} +

+
+ + {data.stats && data.stats?.length <= 4 &&
+ {data.stats?.map((stat, index) => ( +
+

+ {stat?.value} +

+

+ {stat?.label} +

+
+ ))} +
} + + + + {data.stats && data.stats?.length > 4 && data.stats?.length <= 8 && (() => { + const rightArray = data.stats?.slice(0, Math.floor(data.stats?.length / 2)); + const leftArray = data.stats?.slice(Math.floor(data.stats?.length / 2)); + + return ( +
+
+ + {leftArray?.map((stat: any, index: number) => ( +
+

+ {stat?.value} +

+

+ {stat?.label} +

+
+ ))} +
+
+ + {rightArray?.map((stat: any, index: number) => ( +
+

+ {stat.value} +

+

+ {stat.label} +

+
+ ))} +
+
+ ); + })()}
- - {data.stats && data.stats?.length <= 4 &&
- {data.stats?.map((stat, index) => ( -
-

- {stat?.value} -

-

- {stat?.label} -

-
- ))} -
} - - - - {data.stats && data.stats?.length > 4 && data.stats?.length <= 8 && (() => { - const rightArray = data.stats?.slice(0, Math.floor(data.stats?.length / 2)); - const leftArray = data.stats?.slice(Math.floor(data.stats?.length / 2)); - - return ( -
-
- - {leftArray?.map((stat: any, index: number) => ( -
-

- {stat?.value} -

-

- {stat?.label} -

-
- ))} -
-
- - {rightArray?.map((stat: any, index: number) => ( -
-

- {stat.value} -

-

- {stat.label} -

-
- ))} -
-
- ); - })()}
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx index 9204630b..5c3f25cd 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTableOfContentsSlide.tsx @@ -46,38 +46,42 @@ export type SchemaType = z.infer; const EducationTableOfContentsSlide = ({ data }: { data: Partial }) => { return ( -
+ <> + + +
-
-
-

- {data.title} -

-
+
+
+

+ {data.title} +

+
-
-
- {data.items?.map((item, index) => ( -
- - {item.number} - - - {item.label} - -
- ))} +
+
+ {data.items?.map((item, index) => ( +
+ + {item.number} + + + {item.label} + +
+ ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx index 85777e53..01de94d1 100644 --- a/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Education/EducationTimelineSlide.tsx @@ -46,25 +46,29 @@ const EducationTimelineSlide = ({ data }: { data: Partial }) => { const isSixOrLess = milestones?.length && milestones?.length <= 6; return ( -
-
-

- {title} -

-
+ <> - {isSixOrLess ? ( - - ) : ( - - )} -
+ +
+
+

+ {title} +

+
+ + {isSixOrLess ? ( + + ) : ( + + )} +
+ ); }; @@ -76,6 +80,7 @@ function TimelineUpToSix({ return ( +
}) => { const series = chartData?.series ?? []; return ( -
+ <> + +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
+
+

+ {title} +

+
-
-
- {items?.map((item, index) => ( -
-
-
- {itemIcon?.__icon_query__} +
+
+ {items?.map((item, index) => ( +
+
+
+ + {/* {itemIcon?.__icon_query__} */} +
+

+ {item.title} +

-

- {item.title} -

+ {item.description} +

-

- {item.description} -

-
- ))} -
- -
-
- - - + ))}
-
- -

{legendLabel}

+ +
+
+ + + +
+
+ +

{legendLabel}

+
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/BulletListWithIconTitleDescriptionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/BulletListWithIconTitleDescriptionSlide.tsx index 8875d162..67d4d84c 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/BulletListWithIconTitleDescriptionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/BulletListWithIconTitleDescriptionSlide.tsx @@ -1,3 +1,4 @@ +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; import * as z from "zod"; @@ -52,62 +53,72 @@ const DataAnalysisListSlide = ({ data }: { data: Partial }) => { const { title, itemIcon, items } = data; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
+
+

+ {title} +

+
-
- {items?.map((item, index) => ( -
-
-
- {itemIcon?.__icon_query__} +
+ {items?.map((item, index) => ( +
+
+
+ + {/* {itemIcon?.__icon_query__} */} +
+

+ {item.title} +

-

- {item.title} -

+ {item.description} +

-

- {item.description} -

-
- ))} + ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx index c8feb8d7..92f1ac42 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/DataAnalysisDashboardSlide.tsx @@ -15,6 +15,7 @@ import { flexibleChartTypeSchema, type FlexibleChartData, } from "./flexibleReportChart"; +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; const SummaryCardSchema = z.object({ value: z.string().min(1).max(8).meta({ @@ -141,12 +142,19 @@ function SummaryCard({ borderColor: "var(--stroke,#ECF5FE)", }} > - + {/* {iconAlt + /> */}

}) => const otherHalfChart = charts?.slice(Math.ceil(charts.length / 2)); return ( -

+ <> +
- -
-

- {title} -

-
- - {summaryCards && summaryCards.length > 0 &&
- {summaryCards?.map((card, index) => ( - - ))} -
} -
+ backgroundColor: "var(--background-color,#F9F8F8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
- {halfChart && halfChart.length > 0 &&
-
+

- {halfChart?.map((chart, index) => ( -
+
- > - -
- -
-

- ))} -
+ {summaryCards && summaryCards.length > 0 &&
+ {summaryCards?.map((card, index) => ( + + ))}
} - {otherHalfChart && otherHalfChart.length > 0 &&
-
- {otherHalfChart?.map((chart, index) => ( -
-
- +
+ + {halfChart && halfChart.length > 0 &&
+
+ {halfChart?.map((chart, index) => ( +
+ +
- - +
-
- ))} -
-
} + ))} +
+
} + {otherHalfChart && otherHalfChart.length > 0 &&
+
+ {otherHalfChart?.map((chart, index) => ( +
+
+ + + + +
+
+ ))} +
+
} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx index 75bb188b..bec43bff 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/HorizontalHeightSpanningImagesWithTitleSlide.tsx @@ -79,47 +79,50 @@ export type SchemaType = z.infer; const TeamSlide = ({ data }: { data: Partial }) => { return ( -
-
+ +
- {data?.members?.map((member, index) => ( -
- {member.image.__image_prompt__} +
+ {data?.members?.map((member, index) => (
-
-

- {member.title} -

-

- {member.subtext} -

+ key={`${member.title}-${index}`} + className="relative h-full overflow-hidden" + > + {member.image.__image_prompt__} +
+
+

+ {member.title} +

+

+ {member.subtext} +

+
-
- ))} + ))} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/IntroCoverSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/IntroCoverSlide.tsx index bbd15bb7..a53a5a45 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/IntroCoverSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/IntroCoverSlide.tsx @@ -23,46 +23,49 @@ const IntroSlide = ({ data }: { data: Partial }) => { const { titleFirstLine, titleSecondLine, name, position } = data; return ( -
- - - - - - - - - -
-

- {titleFirstLine} -

-

- {titleSecondLine} -

-
+ <> +
-
-

{name}

-

{position}

-
+ className='relative w-[1280px] h-[720px] aspect-video flex flex-col justify-center items-center bg-white' + style={{ + backgroundColor: "var(--background-color,#ffffff)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > + + + + + + + + -
+
+

+ {titleFirstLine} +

+

+ {titleSecondLine} +

+
+
+
+

{name}

+

{position}

+
+ +
+ ) } diff --git a/electron/servers/nextjs/app/presentation-templates/Report/MetricsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MetricsSlide.tsx index 5a155239..ae8e5f6e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/MetricsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/MetricsSlide.tsx @@ -135,54 +135,57 @@ const IntroductionStatsSlide = ({ data }: { data: Partial }) => { const { title, body, bullets, statColumns } = data; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
- -
-
-

- {body} -

- -
+

- {bullets?.map((bullet, index) => ( -
-

- {bullet} -

-
+ {title} +

+
+ +
+
+

+ {body} +

+ +
+ {bullets?.map((bullet, index) => ( +
+

+ {bullet} +

+
+ ))} +
+
+ +
+ {statColumns?.map((column, index) => ( + ))}
- -
- {statColumns?.map((column, index) => ( - - ))} -
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx index 5ba2a67c..3cccddc7 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/MilestoneSlide.tsx @@ -66,72 +66,75 @@ const MilestoneSlide = ({ data }: { data: Partial }) => { const { title, activeIndex, items } = data; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#F9F8F8]" + style={{ + backgroundColor: "var(--background-color,#F9F8F8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
+
+

+ {title} +

+
-
-
- {items?.map((item, index) => { - const isActive = index === activeIndex; +
+
+ {items?.map((item, index) => { + const isActive = index === activeIndex; - return ( -
+ return ( +
-
0 ? "ml-[-45px]" : ""} `} - style={{ - backgroundColor: isActive ? "var(--primary-color,#157CFF)" : "var(--card-color,#ffffff)", - borderColor: "var(--primary-color,#157CFF)", - color: isActive ? "var(--primary-text,#ffffff)" : "var(--primary-color,#157CFE)", - }} - > - 0 ? "ml-[-45px]" : ""} `} + style={{ + backgroundColor: isActive ? "var(--primary-color,#157CFF)" : "var(--card-color,#ffffff)", + borderColor: "var(--primary-color,#157CFF)", + color: isActive ? "var(--primary-text,#ffffff)" : "var(--primary-color,#157CFE)", + }} > - {item.bulletNumber} - + + {item.bulletNumber} + +
+
0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`} + style={{ color: "var(--background-text,#232223)" }} + > +

+ {item.heading} +

+

+ {item.description} +

+
-
0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`} - style={{ color: "var(--background-text,#232223)" }} - > -

- {item.heading} -

-

- {item.description} -

-
-
- ); - })} + ); + })} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TitleChartWithMetricsCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleChartWithMetricsCardsSlide.tsx index 05f0fb6f..fa51c24e 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TitleChartWithMetricsCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleChartWithMetricsCardsSlide.tsx @@ -129,75 +129,78 @@ const DataAnalysisLineStatsSlide = ({ data }: { data: Partial }) => const series = chartData?.series ?? []; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
- -
-
- {chartType === "line-dual" &&
+

- - - {seriesALabel} - - - - {seriesBLabel} - -

} + {title} +

+
-
- - +
+ {chartType === "line-dual" &&
+ + + {seriesALabel} + + + + {seriesBLabel} + +
} + +
+ + + +
+ +
+ - +

{data.legendLabel}

+
-
- -

{data.legendLabel}

+
+ {statColumns?.map((column, index) => ( + + ))}
- -
- {statColumns?.map((column, index) => ( - - ))} -
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionChartSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionChartSlide.tsx index 1f7aac3d..b623161a 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionChartSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionChartSlide.tsx @@ -5,6 +5,7 @@ import * as z from "zod"; import { ResponsiveContainer } from "recharts"; import { FlexibleReportChart, flexibleChartDataSchema } from "./flexibleReportChart"; +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; export const slideLayoutId = "title-description-chart-slide"; export const slideLayoutName = "Title Description Chart Slide"; @@ -67,70 +68,81 @@ const DataAnalysisInsightBarSlide = ({ const series = data?.chartData?.series ?? []; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {data.title} -

-
- -
-
-
-
- {data.insightIcon?.__icon_query__} -
-
-

+

- {data.insightBody} -

+ {data.title} +

-
- - - -
- -

{data.legendLabel}

+
+
+
+
+ + + {/* {data.insightIcon?.__icon_query__} */} +
+
+

+ {data.insightBody} +

+
+ +
+ + + +
+ +

{data.legendLabel}

+
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx index 23023436..eff3ed25 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleDescriptionImageSlide.tsx @@ -46,61 +46,64 @@ const IntroductionImageSlide = ({ data }: { data: Partial }) => { const { title, body, bullets, featureImage } = data; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
- -
-
-

- {body} -

- -
+

- {bullets?.map((bullet, index) => ( -
-

- {bullet} -

-
- ))} -
+ {title} +

-
-
- {featureImage?.__image_prompt__} +
+
+

+ {body} +

+ +
+ {bullets?.map((bullet, index) => ( +
+

+ {bullet} +

+
+ ))} +
+
+ +
+
+ {featureImage?.__image_prompt__} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TitleImageBulletCardsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleImageBulletCardsSlide.tsx index dfc1c3e0..912f9ba6 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TitleImageBulletCardsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleImageBulletCardsSlide.tsx @@ -89,45 +89,58 @@ const SolutionSlide = ({ data }: SolutionSlideProps) => { const { title, showImage, featureImage, cards } = data; const visibleCards = showImage ? cards?.slice(0, 2) : cards; + return ( -
-
+ <> +
+
-
- {title && ( -

- {title} -

- )} +
+ {title && ( +

+ {title} +

+ )} -
- {showImage ? ( -
- {featureImage?.__image_url__ && ( -
- {featureImage?.__image_prompt__} +
+ {showImage ? ( +
+ {featureImage?.__image_url__ && ( +
+ {featureImage?.__image_prompt__} +
+ )} + +
+ {visibleCards?.map((card, index) => ( + + ))}
- )} - -
+
+ ) : ( +
{visibleCards?.map((card, index) => ( { /> ))}
-
- ) : ( -
- {visibleCards?.map((card, index) => ( - - ))} -
- )} + )} +
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TitleMetricsSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleMetricsSlide.tsx index a1802442..a35b2107 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TitleMetricsSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleMetricsSlide.tsx @@ -73,6 +73,7 @@ function StatPill({ }) { return ( +
}) => { const { title, columns } = data; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

+
+

+ {title} +

+
+ +
+ {columns?.map((column, index) => ( + + ))} +
- -
- {columns?.map((column, index) => ( - - ))} -
-
+ ); }; diff --git a/electron/servers/nextjs/app/presentation-templates/Report/TitleWorkflowWithTitleDescriptionSlide.tsx b/electron/servers/nextjs/app/presentation-templates/Report/TitleWorkflowWithTitleDescriptionSlide.tsx index 8197f075..2a877409 100644 --- a/electron/servers/nextjs/app/presentation-templates/Report/TitleWorkflowWithTitleDescriptionSlide.tsx +++ b/electron/servers/nextjs/app/presentation-templates/Report/TitleWorkflowWithTitleDescriptionSlide.tsx @@ -1,6 +1,7 @@ import * as z from "zod"; import { Fragment } from "react/jsx-runtime"; +import { RemoteSvgIcon } from "@/app/hooks/useRemoteSvgIcon"; const ServiceItemSchema = z.object({ icon: z.object({ @@ -108,85 +109,94 @@ const ServicesSlide = ({ data }: { data: Partial }) => { const { title, activeIndex, items } = data; return ( -
+ <> +
+ className="relative h-[720px] w-[1280px] overflow-hidden rounded-[24px] bg-[#f9f8f8]" + style={{ + backgroundColor: "var(--background-color,#f9f8f8)", + fontFamily: "var(--body-font-family,'Source Sans 3')", + }} + > +
-
-

- {title} -

-
+
+

+ {title} +

+
-
- {items?.map((item, index) => { - const isActive = index === activeIndex; +
+ {items?.map((item, index) => { + const isActive = index === activeIndex; - return ( - -
-
- + return ( + +
+
+ {/* */} + +
+ +

+ {item.heading} +

+

+ {item.description} +

-

- {item.heading} -

-

- {item.description} -

-
- - {index < items?.length - 1 && ( -
- {/*
+ {/*
}) => { strokeLinejoin="round" /> */} - - - -
- )} - - ); - })} + + + +
+ )} + + ); + })} +
-
+ ); };