ppt-tool/frontend/app/presentation-templates/general/TableInfoSlideLayout.tsx
Vadym Samoilenko cf21ba4516 Phase 1-2: Foundation + Admin Panel & Client Management
Phase 1 (Foundation):
- Project restructure (presenton-main → backend/ + frontend/)
- Database schema (8 new models, Alembic config, seed script)
- Auth (Azure AD SSO + dev bypass, JWT sessions, AuthMiddleware)
- RBAC (access_service, rbac_middleware, admin routers)
- Audit logging (fire-and-forget, AuditMiddleware, admin router)
- i18n (react-i18next with 5 namespace files)

Phase 2 (Admin Panel & Client Management):
- Admin panel shell (sidebar layout, role guard, 12 pages)
- Redux admin slice with 18 async thunks
- User management (role changes, deactivation)
- Client management (CRUD, brand config, team management)
- Brand config editor (colors, fonts, logos, voice rules)
- Master deck upload & parser (PPTX → HTML → React pipeline)
- Audit log viewer with filters and CSV/JSON export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:37:17 +00:00

170 lines
No EOL
9.6 KiB
TypeScript

import React from 'react'
import * as z from "zod";
export const layoutId = 'table-info-slide'
export const layoutName = 'Table with Info'
export const layoutDescription = 'A slide layout with a title at the top, structured table in the middle, and descriptive text at the bottom.'
const tableInfoSlideSchema = z.object({
title: z.string().min(3).max(40).default('Market Comparison').meta({
description: "Main title of the slide",
}),
tableData: z.object({
headers: z.array(z.string().min(1).max(30)).min(2).max(5).meta({
description: "Table column headers"
}),
rows: z.array(z.array(z.string().min(1).max(50))).min(2).max(6).meta({
description: "Table rows data - each row should match the number of headers"
})
}).default({
headers: ['Company', 'Revenue', 'Growth', 'Market Share'],
rows: [
['Company A', '$2.5M', '15%', '25%'],
['Company B', '$1.8M', '12%', '18%'],
['Company C', '$3.2M', '20%', '32%'],
['Our Company', '$1.2M', '35%', '12%']
]
}).meta({
description: "Table structure with headers and rows"
}),
description: z.string().min(10).max(200).default('This comparison shows our competitive position in the market. While we currently have a smaller market share, our growth rate significantly exceeds competitors, indicating strong potential for future expansion.').meta({
description: "Descriptive text that appears below the table",
})
})
export const Schema = tableInfoSlideSchema
export type TableInfoSlideData = z.infer<typeof tableInfoSlideSchema>
interface TableInfoSlideLayoutProps {
data?: Partial<TableInfoSlideData>
}
const TableInfoSlideLayout: React.FC<TableInfoSlideLayoutProps> = ({ data: slideData }) => {
const tableHeaders = slideData?.tableData?.headers || ['Company', 'Revenue', 'Growth', 'Market Share']
const tableRows = slideData?.tableData?.rows || [
['Company A', '$2.5M', '15%', '25%'],
['Company B', '$1.8M', '12%', '18%'],
['Company C', '$3.2M', '20%', '32%'],
['Our Company', '$1.2M', '35%', '12%']
]
return (
<>
{/* Import Google Fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden flex flex-col"
style={{
fontFamily: 'var(--heading-font-family,Poppins)',
background: "var(--background-color,#ffffff)"
}}
>
{((slideData as any)?.__companyName__ || (slideData as any)?._logo_url__) && (
<div className="absolute top-0 left-0 right-0 px-8 sm:px-12 lg:px-20 pt-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
{(slideData as any)?._logo_url__ && <img src={(slideData as any)?._logo_url__} alt="logo" className="w-6 h-6" />}
{(slideData as any)?.__companyName__ && <span className="text-sm sm:text-base font-semibold" style={{ color: 'var(--background-text, #111827)' }}>
{(slideData as any)?.__companyName__ || 'Company Name'}
</span>}
</div>
</div>
</div>
)}
{/* Decorative Wave Patterns */}
<div className="absolute top-0 left-0 w-64 h-full opacity-10 overflow-hidden">
<svg className="w-full h-full" viewBox="0 0 200 400" fill="none">
<path d="M0 100C50 150 100 50 150 100C175 125 200 100 200 100V0H0V100Z" fill="var(--primary-color,#9333ea)" opacity="0.3" />
<path d="M0 200C75 250 125 150 200 200V150C150 175 100 150 50 175L0 200Z" fill="var(--primary-color,#9333ea)" opacity="0.2" />
<path d="M0 300C100 350 150 250 200 300V250C125 275 75 250 25 275L0 300Z" fill="var(--primary-color,#9333ea)" opacity="0.1" />
</svg>
</div>
<div className="absolute top-0 right-0 w-64 h-full opacity-10 overflow-hidden transform scale-x-[-1]">
<svg className="w-full h-full" viewBox="0 0 200 400" fill="none">
<path d="M0 100C50 150 100 50 150 100C175 125 200 100 200 100V0H0V100Z" fill="var(--primary-color,#9333ea)" opacity="0.3" />
<path d="M0 200C75 250 125 150 200 200V150C150 175 100 150 50 175L0 200Z" fill="var(--primary-color,#9333ea)" opacity="0.2" />
<path d="M0 300C100 350 150 250 200 300V250C125 275 75 250 25 275L0 300Z" fill="var(--primary-color,#9333ea)" opacity="0.1" />
</svg>
</div>
{/* Main Content */}
<div className="relative z-10 px-8 sm:px-12 lg:px-20 pt-12 py-8 flex-1 flex flex-col justify-between">
{/* Title Section */}
<div className="text-center space-y-4">
<h1 style={{ color: "var(--background-text,#111827)" }} className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900">
{slideData?.title || 'Market Comparison'}
</h1>
{/* Purple accent line */}
<div style={{ background: "var(--primary-color,#9333ea)" }} className="w-20 h-1 bg-purple-600 mx-auto"></div>
</div>
{/* Table Section */}
<div className="flex-1 flex items-center justify-center py-8">
<div className="w-full max-w-4xl">
<div style={{ background: "var(--card-color, #e5e7eb)", borderColor: "var(--stroke, #e5e7eb)" }} className="bg-white rounded-lg shadow-lg border overflow-hidden">
{/* Table Header */}
<div style={{ backgroundColor: "var(--primary-color,#9333ea)" }}>
<div className="grid gap-px" style={{ gridTemplateColumns: `repeat(${tableHeaders.length}, 1fr)` }}>
{tableHeaders.map((header, index) => (
<div key={index} className="px-6 py-4 font-semibold text-center text-sm sm:text-base" style={{ color: "var(--primary-text,#111827)" }}>
{header}
</div>
))}
</div>
</div>
{/* Table Body */}
<div className="divide-y divide-gray-200 "
// style={{ borderColor: "var(--stroke, #e5e7eb)" }}
>
{tableRows.map((row, rowIndex) => (
<div
key={rowIndex}
className={`grid gap-px border-r transition-colors duration-200`}
style={{ gridTemplateColumns: `repeat(${tableHeaders.length}, 1fr)`, borderColor: "var(--stroke, #e5e7eb)", backgroundColor: "var(--card-color, #e5e7eb)" }}
>
{row.slice(0, tableHeaders.length).map((cell, cellIndex) => (
<div
key={cellIndex}
className="px-6 py-4 text-center text-sm sm:text-base"
style={{
color: "var(--background-text,#4b5563)",
background: cellIndex % 2 === 0
? "var(--card-color, #e5e7eb)"
: "var(--card-color, #f3f4f6)",
}}
>
{cell}
</div>
))}
</div>
))}
</div>
</div>
</div>
</div>
{/* Description Section */}
<div className="text-center space-y-4">
<div className="max-w-4xl mx-auto">
<p style={{ color: "var(--background-text,#4b5563)" }} className="text-sm sm:text-base text-gray-700 leading-relaxed">
{slideData?.description || 'This comparison shows our competitive position in the market. While we currently have a smaller market share, our growth rate significantly exceeds competitors, indicating strong potential for future expansion.'}
</p>
</div>
</div>
</div>
</div>
</>
)
}
export default TableInfoSlideLayout