import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../constants/item_categories.dart'; import '../theme/app_theme.dart'; class WeekScreen extends StatefulWidget { const WeekScreen({super.key}); @override State createState() => _WeekScreenState(); } class _WeekScreenState extends State { DateTime _selectedDay = DateTime.now(); List> _dayItems = []; bool _isLoading = false; static const _weekdayShort = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom']; static const _weekdayLong = [ 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo', ]; @override void initState() { super.initState(); _loadDayItems(); } DateTime get _startOfWeek { final now = DateTime.now(); return DateTime(now.year, now.month, now.day) .subtract(Duration(days: now.weekday - 1)); } String _dateKey(DateTime d) => '${d.year.toString().padLeft(4, '0')}-' '${d.month.toString().padLeft(2, '0')}-' '${d.day.toString().padLeft(2, '0')}'; Future _getOrCreatePlanId(DateTime day) async { final user = Supabase.instance.client.auth.currentUser!; final dateStr = _dateKey(day); final existing = await Supabase.instance.client .from('plans') .select('id') .eq('user_id', user.id) .eq('data', dateStr) .maybeSingle(); if (existing != null) return existing['id'] as int; final created = await Supabase.instance.client .from('plans') .insert({'user_id': user.id, 'data': dateStr}) .select() .single(); return created['id'] as int; } Future _loadDayItems() async { setState(() => _isLoading = true); try { final user = Supabase.instance.client.auth.currentUser; if (user == null) return; final dateStr = _dateKey(_selectedDay); final plan = await Supabase.instance.client .from('plans') .select('id, plan_items(item_id, items(*, item_images(image_url)))') .eq('user_id', user.id) .eq('data', dateStr) .maybeSingle(); if (plan == null) { if (!mounted) return; setState(() { _dayItems = []; _isLoading = false; }); return; } final planItems = plan['plan_items'] as List? ?? []; final items = planItems .where((pi) => pi['items'] != null) .map>( (pi) => Map.from(pi['items']), ) .toList(); if (!mounted) return; setState(() { _dayItems = items; _isLoading = false; }); } catch (e) { debugPrint('Error loading day: $e'); if (mounted) setState(() => _isLoading = false); } } Future _addItemsToDay() async { final user = Supabase.instance.client.auth.currentUser; if (user == null) return; final allItems = await Supabase.instance.client .from('items') .select('*, item_images(image_url)') .eq('user_id', user.id); final available = List>.from(allItems); final existingIds = _dayItems.map((i) => i['id']).toSet(); final toShow = available.where((i) => !existingIds.contains(i['id'])).toList(); if (!mounted) return; final selected = await showModalBottomSheet>( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => _ItemPickerSheet(items: toShow), ); if (selected == null || selected.isEmpty) return; try { final planId = await _getOrCreatePlanId(_selectedDay); final rows = selected.map((id) => {'plan_id': planId, 'item_id': id}).toList(); await Supabase.instance.client.from('plan_items').insert(rows); _loadDayItems(); } catch (e) { if (mounted) AppSnack.error(context, 'Erro: $e'); } } Future _removeItem(Map item) async { try { final planId = await _getOrCreatePlanId(_selectedDay); await Supabase.instance.client .from('plan_items') .delete() .eq('plan_id', planId) .eq('item_id', item['id']); _loadDayItems(); } catch (e) { if (mounted) AppSnack.error(context, 'Erro: $e'); } } @override Widget build(BuildContext context) { final days = List.generate(7, (i) => _startOfWeek.add(Duration(days: i))); return Scaffold( backgroundColor: AppColors.background, body: SafeArea( bottom: false, child: Column( children: [ _buildHeader(), _buildDaysRow(days), const SizedBox(height: 12), _buildDayTitle(), const SizedBox(height: 8), Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _dayItems.isEmpty ? _buildEmpty() : ListView.builder( padding: const EdgeInsets.fromLTRB( 20, 0, 20, 120, ), itemCount: _dayItems.length, itemBuilder: (_, i) => _buildItemTile(_dayItems[i]), ), ), ], ), ), floatingActionButton: _buildFab(), ); } Widget _buildHeader() { return const Padding( padding: EdgeInsets.fromLTRB(20, 12, 20, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Minha Semana', style: AppText.h2), SizedBox(height: 2), Text( 'Planeie o que precisa para cada dia', style: AppText.caption, ), ], ), ); } Widget _buildDaysRow(List days) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: days.map((day) { final isSelected = day.year == _selectedDay.year && day.month == _selectedDay.month && day.day == _selectedDay.day; final today = DateTime.now(); final isToday = day.year == today.year && day.month == today.month && day.day == today.day; return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 3), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.md), onTap: () { setState(() => _selectedDay = day); _loadDayItems(); }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( gradient: isSelected ? AppColors.brandGradient : null, color: isSelected ? null : AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all( color: isToday && !isSelected ? AppColors.primary : AppColors.border, width: isToday ? 1.5 : 1, ), boxShadow: isSelected ? AppShadows.brand : null, ), child: Column( children: [ Text( _weekdayShort[day.weekday - 1], style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: isSelected ? Colors.white : AppColors.textSecondary, ), ), const SizedBox(height: 4), Text( '${day.day}', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: isSelected ? Colors.white : AppColors.textPrimary, ), ), ], ), ), ), ), ), ); }).toList(), ), ); } Widget _buildDayTitle() { return Padding( padding: const EdgeInsets.fromLTRB(20, 8, 20, 8), child: Row( children: [ Text( '${_weekdayLong[_selectedDay.weekday - 1]}, ' '${_selectedDay.day}/${_selectedDay.month}', style: AppText.h3, ), const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.pill), ), child: Text( '${_dayItems.length} ${_dayItems.length == 1 ? "item" : "itens"}', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, fontSize: 12, ), ), ), ], ), ); } Widget _buildEmpty() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: const Icon( Icons.event_available_rounded, color: AppColors.primary, size: 36, ), ), const SizedBox(height: 16), const Text('Nada planeado', style: AppText.h3), const SizedBox(height: 4), Text( 'Toque em + para adicionar itens', style: AppText.caption, ), ], ), ); } Widget _buildItemTile(Map item) { 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?); return Dismissible( key: ValueKey(item['id']), direction: DismissDirection.endToStart, background: Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.symmetric(horizontal: 24), alignment: Alignment.centerRight, decoration: BoxDecoration( color: AppColors.error, borderRadius: BorderRadius.circular(AppRadius.lg), ), child: const Icon(Icons.delete_outline, color: Colors.white), ), onDismissed: (_) => _removeItem(item), child: Container( margin: const EdgeInsets.only(bottom: 10), decoration: AppDecorations.card(), clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(10), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(AppRadius.md), child: Container( width: 56, height: 56, color: cat.color.withValues(alpha: 0.15), child: imageUrl != null ? Image.network( imageUrl, 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: 15, color: AppColors.textPrimary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ Icon(cat.icon, size: 12, color: cat.color), const SizedBox(width: 4), Text( cat.name, style: TextStyle( fontSize: 12, color: cat.color, fontWeight: FontWeight.w600, ), ), ], ), ], ), ), IconButton( icon: const Icon( Icons.close_rounded, color: AppColors.textTertiary, ), onPressed: () => _removeItem(item), ), ], ), ), ), ); } Widget _buildFab() { return Container( decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.lg), boxShadow: AppShadows.brand, ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.lg), onTap: _addItemsToDay, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 18, vertical: 14), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add_rounded, color: Colors.white), SizedBox(width: 6), Text( 'Adicionar', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ); } } // ============================================================ // Item picker bottom sheet // ============================================================ class _ItemPickerSheet extends StatefulWidget { final List> items; const _ItemPickerSheet({required this.items}); @override State<_ItemPickerSheet> createState() => _ItemPickerSheetState(); } class _ItemPickerSheetState extends State<_ItemPickerSheet> { final Set _selected = {}; String _query = ''; @override Widget build(BuildContext context) { final filtered = widget.items .where( (i) => (i['nome'] ?? '') .toString() .toLowerCase() .contains(_query.toLowerCase()), ) .toList(); return DraggableScrollableSheet( initialChildSize: 0.85, maxChildSize: 0.95, minChildSize: 0.5, expand: false, builder: (_, scrollController) => Container( decoration: const BoxDecoration( color: AppColors.background, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( children: [ Container( margin: const EdgeInsets.symmetric(vertical: 10), width: 42, height: 4, decoration: BoxDecoration( color: AppColors.border, borderRadius: BorderRadius.circular(2), ), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 20, vertical: 4), child: Align( alignment: Alignment.centerLeft, child: Text('Adicionar itens ao dia', style: AppText.h3), ), ), Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 8), child: Container( decoration: AppDecorations.outlined(radius: AppRadius.pill), child: TextField( onChanged: (v) => setState(() => _query = v), decoration: const InputDecoration( prefixIcon: Icon( Icons.search_rounded, color: AppColors.textSecondary, ), hintText: 'Pesquisar...', border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 14), ), ), ), ), Expanded( child: filtered.isEmpty ? Center( child: Text( 'Sem itens disponíveis', style: AppText.caption, ), ) : ListView.builder( controller: scrollController, padding: const EdgeInsets.fromLTRB(20, 4, 20, 12), itemCount: filtered.length, itemBuilder: (_, i) => _buildPickerTile(filtered[i]), ), ), SafeArea( top: false, child: Padding( padding: const EdgeInsets.all(20), child: AppButton( label: _selected.isEmpty ? 'Selecione itens' : 'Adicionar (${_selected.length})', icon: Icons.check_rounded, onPressed: _selected.isEmpty ? null : () => Navigator.pop(context, _selected.toList()), ), ), ), ], ), ), ); } Widget _buildPickerTile(Map item) { final id = item['id'] as int; final selected = _selected.contains(id); 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?); return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all( color: selected ? AppColors.primary : AppColors.border, width: selected ? 1.5 : 1, ), ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.md), onTap: () { setState(() { if (selected) { _selected.remove(id); } else { _selected.add(id); } }); }, child: Padding( padding: const EdgeInsets.all(10), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(AppRadius.sm), child: Container( width: 48, height: 48, color: cat.color.withValues(alpha: 0.15), child: imageUrl != null ? Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Icon(cat.icon, color: cat.color, size: 22), ) : Icon(cat.icon, color: cat.color, size: 22), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['nome'] ?? '', style: const TextStyle( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( cat.name, style: TextStyle( fontSize: 12, color: cat.color, fontWeight: FontWeight.w600, ), ), ], ), ), AnimatedContainer( duration: const Duration(milliseconds: 180), width: 24, height: 24, decoration: BoxDecoration( color: selected ? AppColors.primary : Colors.transparent, shape: BoxShape.circle, border: Border.all( color: selected ? AppColors.primary : AppColors.border, width: 2, ), ), child: selected ? const Icon( Icons.check_rounded, color: Colors.white, size: 16, ) : null, ), ], ), ), ), ), ); } }