presenton/servers/nextjs/presentation-layouts/analytics/TimelineTrendSlideLayout.tsx
2025-07-18 20:49:06 +05:45

254 lines
No EOL
12 KiB
TypeScript

import React from 'react'
import * as z from "zod";
export const layoutId = 'timeline-trend-slide'
export const layoutName = 'Timeline / Trend Slide'
export const layoutDescription = 'A layout for displaying time-based milestones with connecting lines and responsive timeline visualization.'
const timelineItemSchema = z.object({
title: z.string().min(2).max(50).meta({ description: "Milestone title" }),
date: z.string().min(2).max(30).meta({ description: "Date or time period" }),
status: z.enum(['completed', 'current', 'upcoming', 'delayed']).default('upcoming').meta({ description: "Milestone status" }),
icon: z.string().optional().meta({ description: "Icon identifier (optional)" }),
description: z.string().optional().meta({ description: "Optional description" }),
});
const timelineTrendSlideSchema = z.object({
title: z.string().min(3).max(100).default('Project Timeline & Milestones').meta({
description: "Main title of the slide",
}),
description: z.string().min(10).max(300).default('This timeline tracks key project milestones and deliverables, providing a clear view of progress and upcoming objectives across the project lifecycle.').meta({
description: "Bottom description text",
}),
timeline: z.array(timelineItemSchema).min(3).max(8).default([
{
title: 'Project Kickoff',
date: 'Jan 2024',
status: 'completed',
description: 'Initial planning and team setup'
},
{
title: 'Research Phase',
date: 'Feb 2024',
status: 'completed',
description: 'Market research and requirements gathering'
},
{
title: 'Design Phase',
date: 'Mar 2024',
status: 'completed',
description: 'UI/UX design and prototyping'
},
{
title: 'Development',
date: 'Apr-Jun 2024',
status: 'current',
description: 'Core development and testing'
},
{
title: 'Beta Testing',
date: 'Jul 2024',
status: 'upcoming',
description: 'User testing and feedback collection'
},
{
title: 'Launch',
date: 'Aug 2024',
status: 'upcoming',
description: 'Product launch and go-to-market'
},
]).meta({
description: "Timeline milestone items (3-8 items)",
}),
showConnectingLines: z.boolean().default(true).meta({
description: "Whether to show connecting lines between milestones",
}),
lineStyle: z.enum(['solid', 'dotted', 'dashed']).default('solid').meta({
description: "Style of connecting lines",
}),
})
export const Schema = timelineTrendSlideSchema
export type TimelineTrendSlideData = z.infer<typeof timelineTrendSlideSchema>
interface TimelineTrendSlideLayoutProps {
data?: Partial<TimelineTrendSlideData>
}
const TimelineTrendSlideLayout: React.FC<TimelineTrendSlideLayoutProps> = ({ data: slideData }) => {
const timeline = slideData?.timeline || [];
const showConnectingLines = slideData?.showConnectingLines ?? true;
const lineStyle = slideData?.lineStyle || 'solid';
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
return 'bg-green-500 border-green-500';
case 'current':
return 'bg-blue-500 border-blue-500';
case 'upcoming':
return 'bg-gray-300 border-gray-300';
case 'delayed':
return 'bg-red-500 border-red-500';
default:
return 'bg-gray-300 border-gray-300';
}
};
const getTextColor = (status: string) => {
switch (status) {
case 'completed':
return 'text-green-600';
case 'current':
return 'text-blue-600';
case 'upcoming':
return 'text-gray-600';
case 'delayed':
return 'text-red-600';
default:
return 'text-gray-600';
}
};
const getLineStyle = () => {
switch (lineStyle) {
case 'dotted':
return 'border-dotted';
case 'dashed':
return 'border-dashed';
default:
return 'border-solid';
}
};
const renderIcon = (status: string) => {
switch (status) {
case 'completed':
return (
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
);
case 'current':
return (
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
</svg>
);
case 'delayed':
return (
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
);
default:
return null;
}
};
return (
<>
{/* Import Google Fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-white relative z-20 mx-auto overflow-hidden"
style={{
fontFamily: 'Nunito, sans-serif'
}}
>
{/* Glass overlay background */}
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
{/* Header section */}
<div className="flex-shrink-0 h-12 sm:h-16 flex items-center justify-center">
<h1
className="text-3xl font-bold text-gray-900 leading-tight text-center"
style={{
fontFamily: 'Space Grotesk, sans-serif'
}}
>
{slideData?.title || 'Project Timeline & Milestones'}
</h1>
</div>
{/* Timeline section */}
<div className="flex-1 w-full overflow-hidden">
<div className="h-full flex items-center justify-center">
<div className="w-full max-w-6xl">
{/* Timeline container */}
<div className="relative">
{/* Timeline items */}
<div className="flex justify-between items-center relative">
{timeline.map((item, index) => (
<div key={index} className="flex flex-col items-center relative z-10">
{/* Circle/Icon */}
<div className={`w-12 h-12 rounded-full border-4 ${getStatusColor(item.status)} flex items-center justify-center shadow-lg bg-white/10 backdrop-blur-sm`}>
{renderIcon(item.status)}
</div>
{/* Label */}
<div className="mt-3 text-center max-w-20">
<div
className={`text-xs font-semibold ${getTextColor(item.status)} mb-1`}
style={{
fontFamily: 'Space Grotesk, sans-serif'
}}
>
{item.title}
</div>
<div
className="text-xs text-gray-600"
style={{
fontFamily: 'Nunito, sans-serif'
}}
>
{item.date}
</div>
{item.description && (
<div
className="text-xs text-gray-500 mt-1 leading-tight"
style={{
fontFamily: 'Nunito, sans-serif'
}}
>
{item.description}
</div>
)}
</div>
</div>
))}
{/* Connecting line */}
{showConnectingLines && (
<div className={`absolute top-6 left-6 right-6 h-0 border-t-2 border-gray-400 ${getLineStyle()} -z-10`} />
)}
</div>
</div>
</div>
</div>
</div>
{/* Bottom Description section */}
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
<p
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
style={{
fontFamily: 'Nunito, sans-serif'
}}
>
{slideData?.description || 'This timeline tracks key project milestones and deliverables, providing a clear view of progress and upcoming objectives across the project lifecycle.'}
</p>
</div>
</div>
</div>
</>
)
}
export default TimelineTrendSlideLayout