F014 - Professional Report Templates
Objective
Generate beautiful, comprehensive analysis reports using React components and templates that authors can view online or export.
Quick Implementation
Using NextSaaS Components
- Card, Badge, Progress components for report UI
- DataDisplay components for metrics
- Tabs for report sections
- Print-optimized styles
New Requirements
- Report template system
- Dynamic data binding
- Print/export styling
- Chart components for visualizations
MVP Implementation
1. Database Schema
-- Report templates
CREATE TABLE analyzer.report_templates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
genre_specific BOOLEAN DEFAULT false,
template_config JSONB NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
-- Generated reports
CREATE TABLE analyzer.analysis_reports (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
manuscript_id UUID REFERENCES analyzer.manuscripts(id),
analysis_session_id UUID REFERENCES analyzer.analysis_sessions(id),
template_id UUID REFERENCES analyzer.report_templates(id),
report_data JSONB NOT NULL,
generated_at TIMESTAMP DEFAULT NOW()
);
-- Seed default template
INSERT INTO analyzer.report_templates (code, name, template_config) VALUES
('standard', 'Standard Analysis Report', '{
"sections": [
"executive_summary",
"genre_analysis",
"structure_analysis",
"character_analysis",
"dialogue_analysis",
"pacing_analysis",
"writing_craft",
"market_readiness",
"improvement_suggestions",
"next_steps"
],
"includeCharts": true,
"includeExamples": true
}');2. Report Component Structure
// packages/ui/src/components/reports/AnalysisReport.tsx
import { Card, Badge, Progress, Tabs, TabsContent } from '@mystoryflow/ui'
import {
RadarChart,
BarChart,
LineChart,
PieChart
} from '@mystoryflow/ui/charts'
interface AnalysisReportProps {
data: AnalysisReportData
template?: ReportTemplate
}
export function AnalysisReport({ data, template }: AnalysisReportProps) {
return (
<div className="analysis-report space-y-8 max-w-4xl mx-auto">
{/* Header */}
<ReportHeader manuscript={data.manuscript} />
{/* Executive Summary */}
<ExecutiveSummary
overallScore={data.overallScore}
strengths={data.topStrengths}
weaknesses={data.topWeaknesses}
genre={data.genre}
/>
{/* Category Scores Overview */}
<CategoryScoresOverview scores={data.categoryScores} />
{/* Detailed Analysis Tabs */}
<Tabs defaultValue="structure" className="mt-8">
<TabsList>
<TabsTrigger value="structure">Structure</TabsTrigger>
<TabsTrigger value="character">Characters</TabsTrigger>
<TabsTrigger value="dialogue">Dialogue</TabsTrigger>
<TabsTrigger value="pacing">Pacing</TabsTrigger>
<TabsTrigger value="craft">Writing Craft</TabsTrigger>
</TabsList>
<TabsContent value="structure">
<StructureAnalysis data={data.structure} />
</TabsContent>
<TabsContent value="character">
<CharacterAnalysis data={data.characters} />
</TabsContent>
<TabsContent value="dialogue">
<DialogueAnalysis data={data.dialogue} />
</TabsContent>
<TabsContent value="pacing">
<PacingAnalysis data={data.pacing} />
</TabsContent>
<TabsContent value="craft">
<WritingCraftAnalysis data={data.writingCraft} />
</TabsContent>
</Tabs>
{/* Market Readiness */}
<MarketReadinessSection data={data.marketReadiness} />
{/* Improvement Roadmap */}
<ImprovementRoadmap suggestions={data.improvements} />
{/* Next Steps */}
<NextSteps
readinessLevel={data.publishingReadiness}
recommendations={data.recommendations}
/>
</div>
)
}
// Sub-components
function ReportHeader({ manuscript }: { manuscript: any }) {
return (
<Card className="p-8 text-center">
<h1 className="text-3xl font-bold mb-2">
Manuscript Analysis Report
</h1>
<h2 className="text-xl text-muted-foreground mb-4">
{manuscript.title}
</h2>
<div className="flex justify-center gap-4 text-sm">
<span>{manuscript.wordCount.toLocaleString()} words</span>
<span>•</span>
<span>{manuscript.genre}</span>
<span>•</span>
<span>{new Date().toLocaleDateString()}</span>
</div>
</Card>
)
}
function ExecutiveSummary({
overallScore,
strengths,
weaknesses,
genre
}: any) {
const getScoreColor = (score: number) => {
if (score >= 85) return 'success'
if (score >= 70) return 'warning'
return 'destructive'
}
return (
<Card className="p-6">
<h3 className="text-xl font-semibold mb-4">Executive Summary</h3>
<div className="grid md:grid-cols-2 gap-6">
<div>
<div className="flex items-center gap-4 mb-4">
<div className="text-4xl font-bold">
{overallScore}%
</div>
<Badge variant={getScoreColor(overallScore)} size="lg">
{overallScore >= 85 ? 'Publication Ready' :
overallScore >= 70 ? 'Needs Revision' :
'Major Work Needed'}
</Badge>
</div>
<Progress value={overallScore} className="h-3 mb-4" />
<p className="text-sm text-muted-foreground">
Your {genre} manuscript scores in the{' '}
<strong>{getPercentile(overallScore)}</strong> percentile
compared to similar works in the genre.
</p>
</div>
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">Top Strengths</h4>
<ul className="list-disc list-inside space-y-1 text-sm">
{strengths.slice(0, 3).map((strength: string, i: number) => (
<li key={i}>{strength}</li>
))}
</ul>
</div>
<div>
<h4 className="font-medium mb-2">Key Areas for Improvement</h4>
<ul className="list-disc list-inside space-y-1 text-sm">
{weaknesses.slice(0, 3).map((weakness: string, i: number) => (
<li key={i}>{weakness}</li>
))}
</ul>
</div>
</div>
</div>
</Card>
)
}
function CategoryScoresOverview({ scores }: { scores: any }) {
const chartData = Object.entries(scores).map(([category, score]) => ({
category: category.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
score: score as number,
fullMark: 100
}))
return (
<Card className="p-6">
<h3 className="text-xl font-semibold mb-4">Analysis Overview</h3>
<div className="h-80">
<RadarChart
data={chartData}
dataKey="score"
categories={chartData.map(d => d.category)}
className="w-full h-full"
/>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 mt-6">
{Object.entries(scores).map(([category, score]) => (
<div key={category} className="text-center">
<div className="text-2xl font-bold">{score}%</div>
<div className="text-sm text-muted-foreground">
{category.replace(/_/g, ' ')}
</div>
</div>
))}
</div>
</Card>
)
}3. Report Generation Service
// packages/manuscript-analysis/src/services/report-generator.ts
export class ReportGenerator {
private supabase = getSupabaseBrowserClient()
async generateReport(
manuscriptId: string,
analysisSessionId: string,
templateCode: string = 'standard'
): Promise<string> {
// Get template
const { data: template } = await this.supabase
.from('analyzer.report_templates')
.select('*')
.eq('code', templateCode)
.single()
// Get all analysis data
const analysisData = await this.gatherAnalysisData(
manuscriptId,
analysisSessionId
)
// Structure report data
const reportData = this.structureReportData(
analysisData,
template.template_config
)
// Save report
const { data: report } = await this.supabase
.from('analyzer.analysis_reports')
.insert({
manuscript_id: manuscriptId,
analysis_session_id: analysisSessionId,
template_id: template.id,
report_data: reportData
})
.select()
.single()
return report.id
}
private async gatherAnalysisData(
manuscriptId: string,
sessionId: string
): Promise<any> {
// Gather all analysis results
const [
manuscript,
analysis,
genre,
improvements
] = await Promise.all([
this.getManuscript(manuscriptId),
this.getAnalysisResults(sessionId),
this.getGenreDetection(manuscriptId),
this.getImprovements(sessionId)
])
return {
manuscript,
analysis,
genre,
improvements,
generatedAt: new Date().toISOString()
}
}
private structureReportData(
data: any,
templateConfig: any
): any {
return {
manuscript: {
title: data.manuscript.title,
wordCount: data.manuscript.word_count,
genre: data.genre.primary_genre
},
overallScore: data.analysis.overall_score,
categoryScores: data.analysis.category_scores,
topStrengths: data.analysis.strengths.slice(0, 5),
topWeaknesses: data.analysis.weaknesses.slice(0, 5),
structure: data.analysis.categories.structure,
characters: data.analysis.categories.character_development,
dialogue: data.analysis.categories.dialogue,
pacing: data.analysis.categories.pacing,
writingCraft: data.analysis.categories.writing_craft,
marketReadiness: data.analysis.market_insights,
publishingReadiness: data.analysis.publishing_readiness,
improvements: data.improvements,
recommendations: this.generateRecommendations(data)
}
}
}4. Report Viewing Page
// apps/analyzer-app/src/app/manuscripts/[id]/report/page.tsx
import { AnalysisReport } from '@mystoryflow/ui/reports'
import { createClient } from '@mystoryflow/database/server'
export default async function ReportPage({
params
}: {
params: { id: string }
}) {
const supabase = getSupabaseBrowserClient()
// Get latest report
const { data: report } = await supabase
.from('analyzer.analysis_reports')
.select('*')
.eq('manuscript_id', params.id)
.order('generated_at', { ascending: false })
.limit(1)
.single()
if (!report) {
return <div>No report found</div>
}
return (
<div className="container py-8">
<div className="mb-8 flex justify-between">
<h1 className="text-3xl font-bold">Analysis Report</h1>
<div className="flex gap-2">
<Button variant="outline" asChild>
<a href={`/api/reports/${report.id}/export?format=pdf`}>
Export PDF
</a>
</Button>
<Button variant="outline" asChild>
<a href={`/api/reports/${report.id}/export?format=html`}>
Export HTML
</a>
</Button>
</div>
</div>
<AnalysisReport data={report.report_data} />
</div>
)
}MVP Acceptance Criteria
- Professional report layout with NextSaaS components
- Executive summary with overall score
- Category breakdown with visualizations
- Detailed analysis sections
- Improvement suggestions
- Market readiness assessment
- Mobile-responsive design
- Print-optimized styles
Post-MVP Enhancements
- Multiple report templates
- Custom branding options
- Interactive charts
- Comparative analysis
- Historical progress tracking
- Email report delivery
- Collaborative annotations
- Video summary generation
Implementation Time
- Development: 2 days
- Testing: 0.5 days
- Total: 2.5 days
Dependencies
- F009-ANALYSIS-FRAMEWORK (analysis data)
- F011-F013 (AI analysis results)
Next Feature
After completion, proceed to F015-EXPORT-SYSTEM for PDF/HTML export.