import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../constants/item_categories.dart'; import '../services/ai_recommendation_service.dart'; import '../theme/app_theme.dart'; import 'add_item_screen.dart'; import 'ai_chat_screen.dart'; import 'item_screen.dart'; import 'perfil_screen.dart'; import 'week_screen.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { int _index = 0; static const _tabs = [ _HomeContent(), ItemScreen(), WeekScreen(), AiChatScreen(), PerfilScreen(), ]; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: IndexedStack(index: _index, children: _tabs), bottomNavigationBar: _buildBottomNav(), ); } Widget _buildBottomNav() { return Container( decoration: BoxDecoration( color: AppColors.surface, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 20, offset: const Offset(0, -4), ), ], ), child: SafeArea( top: false, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _navItem(0, Icons.home_rounded, 'Início'), _navItem(1, Icons.inventory_2_rounded, 'Itens'), _navItem(2, Icons.calendar_month_rounded, 'Semana'), _navItem(3, Icons.auto_awesome_rounded, 'IA'), _navItem(4, Icons.person_rounded, 'Perfil'), ], ), ), ), ); } Widget _navItem(int idx, IconData icon, String label) { final selected = _index == idx; return Expanded( child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.lg), onTap: () => setState(() => _index = idx), child: AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeOut, padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: selected ? AppColors.primary.withValues(alpha: 0.08) : Colors.transparent, borderRadius: BorderRadius.circular(AppRadius.lg), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 24, color: selected ? AppColors.primary : AppColors.textSecondary, ), const SizedBox(height: 3), Text( label, style: TextStyle( fontSize: 11, fontWeight: selected ? FontWeight.w700 : FontWeight.w500, color: selected ? AppColors.primary : AppColors.textSecondary, ), ), ], ), ), ), ), ); } } // ============================================================ // Home tab content // ============================================================ class _HomeContent extends StatefulWidget { const _HomeContent(); @override State<_HomeContent> createState() => _HomeContentState(); } class _HomeContentState extends State<_HomeContent> { int _itemCount = 0; String _userName = ''; List> _todayItems = []; List> _recentItems = []; bool _isLoading = true; static const _weekdayLong = [ 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo', ]; @override void initState() { super.initState(); _loadData(); } String _dateKey(DateTime d) => '${d.year.toString().padLeft(4, '0')}-' '${d.month.toString().padLeft(2, '0')}-' '${d.day.toString().padLeft(2, '0')}'; Future _loadData() async { setState(() => _isLoading = true); try { final user = Supabase.instance.client.auth.currentUser; if (user == null) return; final all = await Supabase.instance.client .from('items') .select('id') .eq('user_id', user.id); final recent = await Supabase.instance.client .from('items') .select('*, item_images(image_url)') .eq('user_id', user.id) .order('id', ascending: false) .limit(8); Map? userRow; try { userRow = await Supabase.instance.client .from('users') .select('nome') .eq('id', user.id) .maybeSingle(); } catch (_) {} final today = DateTime.now(); final plan = await Supabase.instance.client .from('plans') .select('plan_items(items(*, item_images(image_url)))') .eq('user_id', user.id) .eq('data', _dateKey(today)) .maybeSingle(); List> todayItems = []; if (plan != null) { final planItems = plan['plan_items'] as List? ?? []; todayItems = planItems .where((pi) => pi['items'] != null) .map>( (pi) => Map.from(pi['items']), ) .toList(); } if (!mounted) return; setState(() { _itemCount = all.length; _userName = userRow?['nome'] ?? user.email?.split('@').first ?? 'Utilizador'; _recentItems = List>.from(recent); _todayItems = todayItems; _isLoading = false; }); } catch (e) { debugPrint('Error loading home: $e'); if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: SafeArea( bottom: false, child: RefreshIndicator( onRefresh: _loadData, color: AppColors.primary, child: ListView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(20, 20, 20, 100), children: [ _buildGreeting(), const SizedBox(height: 20), _buildHeroCard(), const SizedBox(height: 16), _buildAiSuggestionButton(), const SizedBox(height: 24), _buildSectionHeader('Hoje'), const SizedBox(height: 12), _buildTodaySection(), const SizedBox(height: 24), _buildSectionHeader('Itens recentes'), const SizedBox(height: 12), _buildRecentItems(), const SizedBox(height: 24), _buildAddCta(), ], ), ), ), ); } Widget _buildGreeting() { final hour = DateTime.now().hour; final saudacao = hour < 12 ? 'Bom dia' : hour < 19 ? 'Boa tarde' : 'Boa noite'; return Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(saudacao, style: AppText.bodySecondary), const SizedBox(height: 2), Text(_userName.isEmpty ? 'Olá!' : _userName, style: AppText.h2), ], ), ), Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.surface, shape: BoxShape.circle, boxShadow: AppShadows.soft, ), child: const Icon( Icons.notifications_none_rounded, color: AppColors.textPrimary, ), ), ], ); } Widget _buildHeroCard() { final today = DateTime.now(); final dayName = _weekdayLong[today.weekday - 1]; return Container( decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.xl), boxShadow: AppShadows.brand, ), padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( Icons.calendar_today_rounded, color: Colors.white, size: 18, ), const SizedBox(width: 8), Text( '$dayName, ${today.day}/${today.month}', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 14), Row( children: [ _heroStat( value: '${_todayItems.length}', label: 'Hoje', icon: Icons.today_rounded, ), const SizedBox(width: 12), _heroStat( value: '$_itemCount', label: 'No inventário', icon: Icons.inventory_2_rounded, ), ], ), ], ), ); } Widget _heroStat({ required String value, required String label, required IconData icon, }) { return Expanded( child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all(color: Colors.white.withValues(alpha: 0.25)), ), child: Row( children: [ Icon(icon, color: Colors.white, size: 22), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( value, style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, height: 1, ), ), const SizedBox(height: 2), Text( label, style: TextStyle( color: Colors.white.withValues(alpha: 0.85), fontSize: 12, ), ), ], ), ], ), ), ); } Widget _buildSectionHeader(String title) { return Text(title, style: AppText.h3); } Widget _buildTodaySection() { if (_isLoading) { return _skeletonRow(); } if (_todayItems.isEmpty) { return Container( padding: const EdgeInsets.all(18), decoration: AppDecorations.card(), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.md), ), child: const Icon( Icons.event_available_rounded, color: AppColors.primary, ), ), const SizedBox(width: 12), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Nada planeado para hoje', style: AppText.body), SizedBox(height: 2), Text( 'Vá à aba Semana para organizar', style: AppText.caption, ), ], ), ), ], ), ); } return SizedBox( height: 130, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _todayItems.length, itemBuilder: (_, i) => _itemCard(_todayItems[i], compact: true), ), ); } Widget _buildRecentItems() { if (_isLoading) return _skeletonRow(); if (_recentItems.isEmpty) { return Container( padding: const EdgeInsets.all(18), decoration: AppDecorations.card(), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.accent.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(AppRadius.md), ), child: const Icon(Icons.add_box_rounded, color: AppColors.accent), ), const SizedBox(width: 12), const Expanded( child: Text('Adicione o seu primeiro item', style: AppText.body), ), ], ), ); } return SizedBox( height: 160, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _recentItems.length, itemBuilder: (_, i) => _itemCard(_recentItems[i]), ), ); } Widget _itemCard(Map item, {bool compact = false}) { final images = item['item_images'] as List?; final imageUrl = (images != null && images.isNotEmpty) ? images.first['image_url'] as String? : null; final cat = categoryById(item['categoria'] as String?); final size = compact ? 110.0 : 130.0; return Container( width: size, margin: const EdgeInsets.only(right: 12), decoration: AppDecorations.card(), clipBehavior: Clip.antiAlias, child: Material( color: Colors.transparent, child: InkWell( onTap: () {}, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ Container( width: double.infinity, height: compact ? 72 : 90, color: cat.color.withValues(alpha: 0.15), child: imageUrl != null ? Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (_, _, _) => Center( child: Icon(cat.icon, color: cat.color, size: 32), ), ) : Center( child: Icon(cat.icon, color: cat.color, size: 32), ), ), ], ), Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['nome'] ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( cat.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 11, color: cat.color, fontWeight: FontWeight.w600, ), ), ], ), ), ], ), ), ), ); } Widget _skeletonRow() { return SizedBox( height: 130, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: 3, itemBuilder: (_, _) => Container( width: 110, margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( color: AppColors.surfaceAlt, borderRadius: BorderRadius.circular(AppRadius.lg), ), ), ), ); } Widget _buildAiSuggestionButton() { return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.lg), onTap: () => _requestAiSuggestion(), child: Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( gradient: AppColors.warmGradient, borderRadius: BorderRadius.circular(AppRadius.lg), boxShadow: [ BoxShadow( color: AppColors.accent.withValues(alpha: 0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.25), borderRadius: BorderRadius.circular(AppRadius.md), ), child: const Icon( Icons.auto_awesome, color: Colors.white, size: 24, ), ), const SizedBox(width: 14), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Pedir sugestao a IA', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white, ), ), SizedBox(height: 2), Text( 'Monta um outfit para o teu dia', style: TextStyle(fontSize: 12, color: Colors.white70), ), ], ), ), const Icon( Icons.arrow_forward_ios_rounded, size: 16, color: Colors.white70, ), ], ), ), ), ); } Future _askOccasion() async { final controller = TextEditingController(); final suggestions = [ 'Piquenique no parque', 'Viagem de 4h de onibus', 'Dia de praia', 'Reuniao de trabalho', 'Jantar fora', ]; return showDialog( context: context, builder: (ctx) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.xl), ), backgroundColor: AppColors.surface, child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.md), ), child: const Icon( Icons.auto_awesome, color: Colors.white, size: 18, ), ), const SizedBox(width: 10), const Expanded( child: Text('Pedir sugestao', style: AppText.h3), ), ], ), const SizedBox(height: 4), const Text( 'Diz a ocasiao para a IA sugerir o que levares.', style: AppText.caption, ), const SizedBox(height: 16), Container( decoration: AppDecorations.outlined(), child: TextField( controller: controller, autofocus: true, textInputAction: TextInputAction.send, onSubmitted: (v) => Navigator.pop(ctx, v.trim()), style: AppText.body, decoration: const InputDecoration( hintText: 'Ex: piquenique no parque', border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 14, vertical: 12, ), ), ), ), const SizedBox(height: 12), const Text('Sugestoes rapidas', style: AppText.label), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: suggestions .map( (s) => AppChip(label: s, onTap: () => Navigator.pop(ctx, s)), ) .toList(), ), const SizedBox(height: 20), Row( children: [ Expanded( child: TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Cancelar'), ), ), const SizedBox(width: 8), Expanded( child: AppButton( label: 'Pedir', icon: Icons.send_rounded, onPressed: () => Navigator.pop(ctx, controller.text.trim()), ), ), ], ), ], ), ), ), ); } Future _requestAiSuggestion() async { final occasion = await _askOccasion(); if (occasion == null || occasion.trim().isEmpty) return; if (!mounted) return; final service = AiRecommendationService(); showDialog( context: context, barrierDismissible: false, builder: (_) => Center( child: Container( padding: const EdgeInsets.all(28), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), ), child: const Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: AppColors.primary), SizedBox(height: 16), Text('A pedir sugestao...', style: AppText.caption), ], ), ), ), ); final allItems = await service.getItemsWithImages(); final response = await service.sendMessage(occasion.trim(), silent: true); if (!mounted) return; Navigator.of(context).pop(); final lines = response .split('\n') .map((l) => l.replaceAll(RegExp(r'^[-•*\d.)\s]+'), '').trim()) .where((l) => l.isNotEmpty) .toList(); final matched = >[]; for (final item in allItems) { final nome = (item['nome'] ?? '').toString().toLowerCase(); for (final line in lines) { if (nome.isNotEmpty && (line.toLowerCase().contains(nome) || nome.contains(line.toLowerCase()))) { matched.add(item); break; } } } if (!mounted) return; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => _AiSuggestionSheet( matchedItems: matched, rawResponse: response, allItems: allItems, ), ); } Widget _buildAddCta() { return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.lg), onTap: () { Navigator.of(context) .push(MaterialPageRoute(builder: (_) => const AddItemScreen())) .then((_) => _loadData()); }, child: Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all( color: AppColors.primary.withValues(alpha: 0.3), style: BorderStyle.solid, width: 1.5, ), ), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.md), ), child: const Icon( Icons.add_rounded, color: Colors.white, size: 24, ), ), const SizedBox(width: 14), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Adicionar item', style: AppText.h3), SizedBox(height: 2), Text( 'Foto, categoria e tags em segundos', style: AppText.caption, ), ], ), ), const Icon( Icons.arrow_forward_ios_rounded, size: 16, color: AppColors.textTertiary, ), ], ), ), ), ); } } class _AiSuggestionSheet extends StatelessWidget { final List> matchedItems; final String rawResponse; final List> allItems; const _AiSuggestionSheet({ required this.matchedItems, required this.rawResponse, required this.allItems, }); String? _imageUrl(Map item) { final images = item['item_images'] as List?; if (images != null && images.isNotEmpty) { return images.first['image_url'] as String?; } return null; } @override Widget build(BuildContext context) { return Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.75, ), margin: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 16, 12, 0), child: Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.md), ), child: const Icon( Icons.auto_awesome, color: Colors.white, size: 18, ), ), const SizedBox(width: 10), const Expanded( child: Text('Sugestao da IA', style: AppText.h3), ), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close_rounded), ), ], ), ), const Divider(height: 16), if (matchedItems.isNotEmpty) Flexible( child: ListView.separated( shrinkWrap: true, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: matchedItems.length, separatorBuilder: (_, __) => const SizedBox(height: 8), itemBuilder: (_, i) { final item = matchedItems[i]; final cat = categoryById(item['categoria'] as String?); final imgUrl = _imageUrl(item); return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppColors.surfaceAlt, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all(color: AppColors.border), ), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(AppRadius.sm), child: Container( width: 52, height: 52, color: cat.color.withValues(alpha: 0.15), child: imgUrl != null ? Image.network( imgUrl, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Icon( cat.icon, color: cat.color, size: 24, ), ) : Icon(cat.icon, color: cat.color, size: 24), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['nome'] ?? '', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Row( children: [ Icon(cat.icon, size: 12, color: cat.color), const SizedBox(width: 4), Text( cat.name, style: TextStyle( fontSize: 11, color: cat.color, fontWeight: FontWeight.w600, ), ), ], ), ], ), ), ], ), ); }, ), ) else Padding( padding: const EdgeInsets.all(16), child: Text(rawResponse, style: AppText.body), ), const SizedBox(height: 12), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( children: [ Expanded( child: _ActionBtn( icon: Icons.calendar_month_rounded, label: 'Exportar para dia', onTap: () => _exportToDay(context), ), ), ], ), ), ], ), ); } void _exportToDay(BuildContext context) { if (matchedItems.isEmpty) { Navigator.pop(context); return; } final now = DateTime.now(); final startOfWeek = DateTime( now.year, now.month, now.day, ).subtract(Duration(days: now.weekday - 1)); final days = List.generate(7, (i) => startOfWeek.add(Duration(days: i))); const dayNames = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab', 'Dom']; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(ctx).size.height * 0.75, ), margin: const EdgeInsets.all(12), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Escolher dia', style: AppText.h3), const SizedBox(height: 4), const Text( 'Exportar itens sugeridos para qual dia?', style: AppText.caption, ), const SizedBox(height: 16), Flexible( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: List.generate(7, (i) { final d = days[i]; final isToday = d.day == now.day && d.month == now.month && d.year == now.year; return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.md), onTap: () async { Navigator.pop(ctx); Navigator.pop(context); await _saveToDay(d, context); }, child: Padding( padding: const EdgeInsets.symmetric( vertical: 12, horizontal: 8, ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: isToday ? AppColors.primary.withValues( alpha: 0.12, ) : AppColors.surfaceAlt, borderRadius: BorderRadius.circular( AppRadius.md, ), ), child: Center( child: Text( '${d.day}', style: TextStyle( fontWeight: FontWeight.w700, color: isToday ? AppColors.primary : AppColors.textPrimary, ), ), ), ), const SizedBox(width: 12), Expanded( child: Text( '${dayNames[i]}${isToday ? ' (hoje)' : ''}', style: TextStyle( fontWeight: FontWeight.w600, color: isToday ? AppColors.primary : AppColors.textPrimary, ), ), ), const Icon( Icons.arrow_forward_ios_rounded, size: 14, color: AppColors.textTertiary, ), ], ), ), ), ); }), ), ), ), ], ), ), ); } Future _saveToDay(DateTime day, BuildContext context) async { try { final user = Supabase.instance.client.auth.currentUser; if (user == null) return; final dateStr = '${day.year.toString().padLeft(4, '0')}-${day.month.toString().padLeft(2, '0')}-${day.day.toString().padLeft(2, '0')}'; final existing = await Supabase.instance.client .from('plans') .select('id') .eq('user_id', user.id) .eq('data', dateStr) .maybeSingle(); final int planId; if (existing != null) { planId = existing['id'] as int; } else { final created = await Supabase.instance.client .from('plans') .insert({'user_id': user.id, 'data': dateStr}) .select() .single(); planId = created['id'] as int; } final existingItems = await Supabase.instance.client .from('plan_items') .select('item_id') .eq('plan_id', planId); final existingIds = (existingItems as List) .map((e) => e['item_id']) .toSet(); final toInsert = matchedItems .where((item) => !existingIds.contains(item['id'])) .map((item) => {'plan_id': planId, 'item_id': item['id']}) .toList(); if (toInsert.isNotEmpty) { await Supabase.instance.client.from('plan_items').insert(toInsert); } if (context.mounted) { AppSnack.success( context, '${matchedItems.length} itens exportados para ${day.day}/${day.month}', ); } } catch (e) { if (context.mounted) { AppSnack.error(context, 'Erro ao exportar: $e'); } } } } class _ActionBtn extends StatelessWidget { final IconData icon; final String label; final VoidCallback onTap; const _ActionBtn({ required this.icon, required this.label, required this.onTap, }); @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.md), onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.md), boxShadow: AppShadows.brand, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: Colors.white, size: 20), const SizedBox(width: 8), Text( label, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 14, ), ), ], ), ), ), ); } }