import 'package:flutter/material.dart'; import 'package:skeletonizer/skeletonizer.dart'; /// Wraps each report chart widget in a themed Card with a title, loading /// skeleton and error state handling. Exposes a [GlobalKey] on the inner /// [RepaintBoundary] so the PDF exporter can capture the rendered chart /// as a raster image. class ReportCardWrapper extends StatelessWidget { const ReportCardWrapper({ super.key, required this.title, required this.child, this.isLoading = false, this.error, this.height, this.repaintBoundaryKey, }); /// Title displayed in the card header. final String title; /// The chart widget to render inside the card. final Widget child; /// Whether to display a loading skeleton. final bool isLoading; /// An error message to display instead of the chart. final String? error; /// Optional fixed height for the chart area. final double? height; /// Key attached to the inner [RepaintBoundary] used for PDF image capture. final GlobalKey? repaintBoundaryKey; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colors = theme.colorScheme; final text = theme.textTheme; Widget body; if (error != null) { body = Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline, color: colors.error, size: 32), const SizedBox(height: 8), Text( error!, style: text.bodyMedium?.copyWith(color: colors.error), textAlign: TextAlign.center, ), ], ), ), ); } else if (isLoading) { body = Skeletonizer( enabled: true, child: Container( height: height ?? 200, decoration: BoxDecoration( color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), ), ); } else { body = child; } final cardContent = Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 14, 16, 4), child: Text(title, style: text.titleSmall), ), const Divider(height: 1), Padding( padding: const EdgeInsets.all(16), child: height != null ? SizedBox(height: height, child: body) : body, ), ], ); final card = Card( elevation: 0, shadowColor: Colors.transparent, color: colors.surfaceContainerLow, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: cardContent, ); if (repaintBoundaryKey != null) { return RepaintBoundary(key: repaintBoundaryKey, child: card); } return card; } }