Skip to Content
📚 MyStoryFlow Docs — Your guide to preserving family stories

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.