From d7c047173669ac101d0d632e3bc88122c2b59c18 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 11 Mar 2026 19:25:35 +0000 Subject: [PATCH] Eliminar creche_app/lib/features/menu/menu_screen.dart --- creche_app/lib/features/menu/menu_screen.dart | 451 ------------------ 1 file changed, 451 deletions(-) delete mode 100644 creche_app/lib/features/menu/menu_screen.dart diff --git a/creche_app/lib/features/menu/menu_screen.dart b/creche_app/lib/features/menu/menu_screen.dart deleted file mode 100644 index e55e24a..0000000 --- a/creche_app/lib/features/menu/menu_screen.dart +++ /dev/null @@ -1,451 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; -import 'package:intl/intl.dart'; -import '/core/auth_provider.dart'; - -const _bg = Color(0xFF0D1117); -const _card = Color(0xFF161B22); -const _blue = Color(0xFF4FC3F7); -const _green = Color(0xFF2ECC71); -const _amber = Color(0xFFFFB300); -const _red = Color(0xFFE74C3C); - -const _mealNames = ['Pequeno Almoço', 'Almoço', 'Lanche da Tarde', 'Jantar']; -const _mealIcons = [Icons.free_breakfast, Icons.lunch_dining, Icons.icecream, Icons.dinner_dining]; -const _weekDays = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta']; - -class MenuScreen extends ConsumerStatefulWidget { - const MenuScreen({super.key}); - @override - ConsumerState createState() => _State(); -} - -class _State extends ConsumerState with SingleTickerProviderStateMixin { - late TabController _tabs; - DateTime _selectedWeek = _startOfWeek(DateTime.now()); - bool _isAdmin = false; - - @override - void initState() { - super.initState(); - _tabs = TabController(length: 2, vsync: this); - _checkRole(); - } - - @override - void dispose() { _tabs.dispose(); super.dispose(); } - - Future _checkRole() async { - final p = await ref.read(currentProfileProvider.future); - if (mounted) setState(() => _isAdmin = p?.role == 'principal' || p?.role == 'admin'); - } - - static DateTime _startOfWeek(DateTime d) { - final diff = d.weekday - 1; - return DateTime(d.year, d.month, d.day - diff); - } - - String get _weekLabel { - final end = _selectedWeek.add(const Duration(days: 4)); - final fmt = DateFormat('d MMM', 'pt_PT'); - return '${fmt.format(_selectedWeek)} – ${fmt.format(end)}'; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: _bg, - appBar: AppBar( - backgroundColor: _card, elevation: 0, - title: const Text('Cardápio', style: TextStyle(color: _blue, fontWeight: FontWeight.bold)), - bottom: TabBar( - controller: _tabs, indicatorColor: _blue, labelColor: _blue, - unselectedLabelColor: Colors.white38, - tabs: const [Tab(text: '📅 Semanal'), Tab(text: '📋 Mensal')], - ), - actions: [ - if (_isAdmin) - IconButton( - icon: const Icon(Icons.add_circle_outline, color: _amber), - tooltip: 'Publicar cardápio', - onPressed: () => _showPublishDialog(context), - ), - ], - ), - body: TabBarView(controller: _tabs, children: [ - _WeeklyMenu(week: _selectedWeek, weekLabel: _weekLabel, - onPrev: () => setState(() => _selectedWeek = _selectedWeek.subtract(const Duration(days: 7))), - onNext: () => setState(() => _selectedWeek = _selectedWeek.add(const Duration(days: 7)))), - const _MonthlyMenu(), - ]), - ); - } - - void _showPublishDialog(BuildContext ctx) { - showModalBottomSheet( - context: ctx, isScrollControlled: true, - backgroundColor: _card, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), - builder: (_) => _PublishMenuForm(week: _selectedWeek), - ); - } -} - -// ── Cardápio Semanal ────────────────────────────────────────────── -class _WeeklyMenu extends StatelessWidget { - final DateTime week; - final String weekLabel; - final VoidCallback onPrev, onNext; - const _WeeklyMenu({required this.week, required this.weekLabel, required this.onPrev, required this.onNext}); - - @override - Widget build(BuildContext context) { - final sb = Supabase.instance.client; - final weekStr = DateFormat('yyyy-MM-dd').format(week); - - return Column(children: [ - // Navegação de semana - Container( - color: _card, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - child: Row(children: [ - IconButton(onPressed: onPrev, icon: const Icon(Icons.chevron_left, color: _blue)), - Expanded(child: Text(weekLabel, - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14))), - IconButton(onPressed: onNext, icon: const Icon(Icons.chevron_right, color: _blue)), - ]), - ), - Expanded( - child: FutureBuilder>>( - future: sb.from('menu_items').select() - .eq('week_start', weekStr) - .order('day_index').order('meal_index'), - builder: (ctx, snap) { - if (snap.hasError) return _err('Erro: ${snap.error}'); - if (!snap.hasData) return const Center(child: CircularProgressIndicator(color: _blue)); - - final items = snap.data!; - if (items.isEmpty) return _emptyMenu(); - - // Agrupar por dia - final Map>> byDay = {}; - for (final item in items) { - final day = (item['day_index'] as int?) ?? 0; - byDay.putIfAbsent(day, () => []).add(item); - } - - return ListView.builder( - padding: const EdgeInsets.all(14), - itemCount: 5, - itemBuilder: (_, i) { - final date = week.add(Duration(days: i)); - final dayMeals = byDay[i] ?? []; - return _DayCard( - dayName: _weekDays[i], - date: DateFormat('d/MM').format(date), - meals: dayMeals, - isToday: DateFormat('yyyy-MM-dd').format(DateTime.now()) == - DateFormat('yyyy-MM-dd').format(date), - ); - }, - ); - }, - ), - ), - ]); - } -} - -class _DayCard extends StatelessWidget { - final String dayName, date; - final List> meals; - final bool isToday; - const _DayCard({required this.dayName, required this.date, required this.meals, required this.isToday}); - - @override - Widget build(BuildContext context) => Container( - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: _card, borderRadius: BorderRadius.circular(16), - border: Border.all(color: isToday ? _blue.withOpacity(0.5) : Colors.white.withOpacity(0.07)), - boxShadow: isToday ? [BoxShadow(color: _blue.withOpacity(0.08), blurRadius: 12)] : null, - ), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header do dia - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - decoration: BoxDecoration( - color: isToday ? _blue.withOpacity(0.12) : Colors.white.withOpacity(0.03), - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - ), - child: Row(children: [ - Text(dayName, style: TextStyle( - color: isToday ? _blue : Colors.white, - fontWeight: FontWeight.bold, fontSize: 14)), - const SizedBox(width: 8), - Text(date, style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 12)), - if (isToday) ...[ - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration(color: _blue.withOpacity(0.2), borderRadius: BorderRadius.circular(10)), - child: const Text('Hoje', style: TextStyle(color: _blue, fontSize: 10, fontWeight: FontWeight.bold)), - ), - ], - ]), - ), - if (meals.isEmpty) - Padding( - padding: const EdgeInsets.all(14), - child: Text('Sem ementa publicada', style: TextStyle(color: Colors.white.withOpacity(0.25), fontSize: 12)), - ) - else - ...meals.map((m) { - final mealIdx = (m['meal_index'] as int?) ?? 0; - final name = mealIdx < _mealIcons.length ? _mealNames[mealIdx] : 'Refeição'; - final icon = mealIdx < _mealIcons.length ? _mealIcons[mealIdx] : Icons.restaurant; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), - child: Row(children: [ - Icon(icon, size: 16, color: _amber), - const SizedBox(width: 10), - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(name, style: const TextStyle(color: Color(0xFF888888), fontSize: 10)), - Text(m['description'] ?? '', style: const TextStyle(color: Colors.white, fontSize: 13)), - ]), - ]), - ); - }), - const SizedBox(height: 4), - ]), - ); -} - -// ── Cardápio Mensal ─────────────────────────────────────────────── -class _MonthlyMenu extends StatefulWidget { - const _MonthlyMenu(); - @override - State<_MonthlyMenu> createState() => _MonthlyState(); -} - -class _MonthlyState extends State<_MonthlyMenu> { - DateTime _month = DateTime(DateTime.now().year, DateTime.now().month); - - @override - Widget build(BuildContext context) { - final sb = Supabase.instance.client; - final monthStr = DateFormat('yyyy-MM').format(_month); - final monthName = DateFormat('MMMM yyyy', 'pt_PT').format(_month); - - return Column(children: [ - Container( - color: _card, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - child: Row(children: [ - IconButton( - onPressed: () => setState(() => _month = DateTime(_month.year, _month.month - 1)), - icon: const Icon(Icons.chevron_left, color: _blue)), - Expanded(child: Text(monthName.toUpperCase(), - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13, letterSpacing: 1))), - IconButton( - onPressed: () => setState(() => _month = DateTime(_month.year, _month.month + 1)), - icon: const Icon(Icons.chevron_right, color: _blue)), - ]), - ), - Expanded( - child: FutureBuilder>>( - future: sb.from('menu_items').select() - .like('week_start', '$monthStr%') - .order('week_start').order('day_index'), - builder: (ctx, snap) { - if (!snap.hasData) return const Center(child: CircularProgressIndicator(color: _blue)); - if (snap.data!.isEmpty) return _emptyMenu(); - // Agrupa por semana - final Map>> byWeek = {}; - for (final item in snap.data!) { - final w = item['week_start'] as String; - byWeek.putIfAbsent(w, () => []).add(item); - } - return ListView( - padding: const EdgeInsets.all(14), - children: byWeek.entries.map((e) { - final weekDt = DateTime.parse(e.key); - final end = weekDt.add(const Duration(days: 4)); - return _WeekSummaryCard( - label: '${DateFormat('d', 'pt_PT').format(weekDt)}–${DateFormat('d MMM', 'pt_PT').format(end)}', - items: e.value, - ); - }).toList(), - ); - }, - ), - ), - ]); - } -} - -class _WeekSummaryCard extends StatelessWidget { - final String label; - final List> items; - const _WeekSummaryCard({required this.label, required this.items}); - - @override - Widget build(BuildContext context) => Container( - margin: const EdgeInsets.only(bottom: 10), - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: _card, borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withOpacity(0.07))), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - const Icon(Icons.calendar_view_week, color: _blue, size: 16), - const SizedBox(width: 6), - Text('Semana de $label', style: const TextStyle(color: _blue, fontWeight: FontWeight.bold, fontSize: 13)), - ]), - const SizedBox(height: 8), - ...items.take(6).map((m) { - final day = (m['day_index'] as int?) ?? 0; - final meal = (m['meal_index'] as int?) ?? 0; - final dayName = day < _weekDays.length ? _weekDays[day] : ''; - final mealName = meal < _mealNames.length ? _mealNames[meal] : ''; - return Padding( - padding: const EdgeInsets.only(bottom: 3), - child: Text('• $dayName – $mealName: ${m['description'] ?? ''}', - style: const TextStyle(color: Color(0xFF888888), fontSize: 12)), - ); - }), - if (items.length > 6) - Text('+${items.length - 6} itens', style: TextStyle(color: Colors.white.withOpacity(0.2), fontSize: 11)), - ]), - ); -} - -// ── Formulário publicar cardápio (admin) ────────────────────────── -class _PublishMenuForm extends ConsumerStatefulWidget { - final DateTime week; - const _PublishMenuForm({required this.week}); - @override - ConsumerState<_PublishMenuForm> createState() => _PublishState(); -} - -class _PublishState extends ConsumerState<_PublishMenuForm> { - int _day = 0, _meal = 0; - final _descCtrl = TextEditingController(); - bool _saving = false; - - @override - void dispose() { _descCtrl.dispose(); super.dispose(); } - - Future _save() async { - if (_descCtrl.text.trim().isEmpty) return; - setState(() => _saving = true); - try { - final sb = Supabase.instance.client; - final weekStr = DateFormat('yyyy-MM-dd').format(widget.week); - await sb.from('menu_items').upsert({ - 'week_start': weekStr, - 'day_index': _day, - 'meal_index': _meal, - 'description': _descCtrl.text.trim(), - }, onConflict: 'week_start,day_index,meal_index'); - _descCtrl.clear(); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Publicado! ✓', style: TextStyle(color: Colors.white)), - backgroundColor: _green, behavior: SnackBarBehavior.floating)); - } - } catch (e) { - if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Erro: $e'), backgroundColor: _red, behavior: SnackBarBehavior.floating)); - } finally { if (mounted) setState(() => _saving = false); } - } - - @override - Widget build(BuildContext context) => Padding( - padding: EdgeInsets.only(left: 20, right: 20, top: 20, bottom: MediaQuery.of(context).viewInsets.bottom + 20), - child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Publicar Ementa', style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold)), - Text('Semana: ${DateFormat('d MMM', 'pt_PT').format(widget.week)}', - style: const TextStyle(color: Color(0xFF888888), fontSize: 12)), - const SizedBox(height: 18), - // Dia - const Text('Dia', style: TextStyle(color: Color(0xFF888888), fontSize: 12)), - const SizedBox(height: 6), - SingleChildScrollView(scrollDirection: Axis.horizontal, - child: Row(children: List.generate(5, (i) => GestureDetector( - onTap: () => setState(() => _day = i), - child: Container( - margin: const EdgeInsets.only(right: 6), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - decoration: BoxDecoration( - color: _day == i ? _blue.withOpacity(0.2) : Colors.white.withOpacity(0.05), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: _day == i ? _blue : Colors.white.withOpacity(0.1))), - child: Text(_weekDays[i], style: TextStyle(color: _day == i ? _blue : Colors.white60, fontSize: 12)), - ), - ))), - ), - const SizedBox(height: 12), - // Refeição - const Text('Refeição', style: TextStyle(color: Color(0xFF888888), fontSize: 12)), - const SizedBox(height: 6), - SingleChildScrollView(scrollDirection: Axis.horizontal, - child: Row(children: List.generate(_mealNames.length, (i) => GestureDetector( - onTap: () => setState(() => _meal = i), - child: Container( - margin: const EdgeInsets.only(right: 6), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: _meal == i ? _amber.withOpacity(0.2) : Colors.white.withOpacity(0.05), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: _meal == i ? _amber : Colors.white.withOpacity(0.1))), - child: Row(children: [ - Icon(_mealIcons[i], size: 14, color: _meal == i ? _amber : Colors.white38), - const SizedBox(width: 4), - Text(_mealNames[i], style: TextStyle(color: _meal == i ? _amber : Colors.white60, fontSize: 12)), - ]), - ), - ))), - ), - const SizedBox(height: 14), - TextField( - controller: _descCtrl, style: const TextStyle(color: Colors.white), - maxLines: 2, - decoration: InputDecoration( - hintText: 'Ex: Arroz com frango e legumes, sumo natural', - hintStyle: const TextStyle(color: Color(0xFF555555), fontSize: 13), - filled: true, fillColor: Colors.white.withOpacity(0.04), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.white.withOpacity(0.09))), - contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), - ), - ), - const SizedBox(height: 16), - GestureDetector( - onTap: _saving ? null : _save, - child: Container( - height: 50, width: double.infinity, - decoration: BoxDecoration( - gradient: const LinearGradient(colors: [_blue, Color(0xFF0288D1)]), - borderRadius: BorderRadius.circular(12)), - child: Center(child: _saving - ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) - : const Text('Publicar', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15))), - ), - ), - ]), - ); -} - -Widget _emptyMenu() => Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.restaurant_menu, size: 60, color: Colors.white.withOpacity(0.08)), - const SizedBox(height: 12), - const Text('Sem ementa publicada para esta semana', style: TextStyle(color: Color(0xFF888888), fontSize: 13)), - const SizedBox(height: 4), - const Text('A diretora ainda não publicou o cardápio.', style: TextStyle(color: Color(0xFF555555), fontSize: 11)), -])); - -Widget _err(String msg) => Center(child: Text(msg, style: const TextStyle(color: _red)));