Eliminar creche_app/lib/features/diary/new_diary_screen.dart
This commit is contained in:
parent
0ec2797511
commit
f2f795cf38
|
|
@ -1,392 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import '../../core/auth_provider.dart';
|
|
||||||
import '../../models/child.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);
|
|
||||||
|
|
||||||
// Opções de refeição
|
|
||||||
const _mealOpts = ['bem', 'pouco', 'nao_aceita'];
|
|
||||||
const _mealLabels = {'bem': '😊 Bem', 'pouco': '😐 Pouco', 'nao_aceita': '😞 Não aceita'};
|
|
||||||
const _hygieneOpts = ['normal', 'diarreia', 'rastoso'];
|
|
||||||
const _hygieneLabels = {'normal': '✅ Normal', 'diarreia': '⚠️ Diarreia', 'rastoso': '😷 Rastoso'};
|
|
||||||
|
|
||||||
class NewDiaryScreen extends ConsumerStatefulWidget {
|
|
||||||
final String? childId;
|
|
||||||
const NewDiaryScreen({super.key, this.childId});
|
|
||||||
@override
|
|
||||||
ConsumerState<NewDiaryScreen> createState() => _State();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _State extends ConsumerState<NewDiaryScreen> {
|
|
||||||
final _actCtrl = TextEditingController();
|
|
||||||
final _notesCtrl = TextEditingController();
|
|
||||||
final _instNotesCtrl = TextEditingController(); // notas da instituição
|
|
||||||
String? _childId;
|
|
||||||
List<Child> _children = [];
|
|
||||||
bool _loading = false;
|
|
||||||
bool _loadingChildren = true;
|
|
||||||
|
|
||||||
// Sono
|
|
||||||
bool _sleepMorning = false;
|
|
||||||
bool _sleepAfternoon = false;
|
|
||||||
|
|
||||||
// Alimentação
|
|
||||||
String _breakfast = '';
|
|
||||||
String _lunch = '';
|
|
||||||
String _snackMeal = '';
|
|
||||||
|
|
||||||
// Higiene
|
|
||||||
int _hygieneFreq = 0;
|
|
||||||
String _hygieneState = '';
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_childId = widget.childId;
|
|
||||||
_loadChildren();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_actCtrl.dispose(); _notesCtrl.dispose(); _instNotesCtrl.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadChildren() async {
|
|
||||||
try {
|
|
||||||
final sb = Supabase.instance.client;
|
|
||||||
final data = await sb.from('children').select().order('full_name');
|
|
||||||
setState(() {
|
|
||||||
_children = data.map((d) => Child.fromMap(d)).toList();
|
|
||||||
_loadingChildren = false;
|
|
||||||
});
|
|
||||||
} catch (_) { setState(() => _loadingChildren = false); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _save() async {
|
|
||||||
if (_childId == null) { _snack('Selecciona uma criança.'); return; }
|
|
||||||
if (_actCtrl.text.trim().isEmpty) { _snack('Descreve as actividades do dia.'); return; }
|
|
||||||
setState(() => _loading = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sb = Supabase.instance.client;
|
|
||||||
final profile = await ref.read(currentProfileProvider.future);
|
|
||||||
if (profile == null) throw Exception('Perfil não encontrado');
|
|
||||||
final today = DateTime.now().toIso8601String().split('T')[0];
|
|
||||||
|
|
||||||
// 1. Criar/actualizar diário
|
|
||||||
final existing = await sb.from('daily_diaries').select('id')
|
|
||||||
.eq('child_id', _childId!).eq('date', today).maybeSingle();
|
|
||||||
|
|
||||||
String diaryId;
|
|
||||||
if (existing != null) {
|
|
||||||
diaryId = existing['id'] as String;
|
|
||||||
await sb.from('daily_diaries').update({
|
|
||||||
'activities': _actCtrl.text.trim(),
|
|
||||||
'notes': _notesCtrl.text.trim(),
|
|
||||||
'institution_notes': _instNotesCtrl.text.trim(),
|
|
||||||
'teacher_id': profile.id,
|
|
||||||
}).eq('id', diaryId);
|
|
||||||
} else {
|
|
||||||
final res = await sb.from('daily_diaries').insert({
|
|
||||||
'child_id': _childId,
|
|
||||||
'teacher_id': profile.id,
|
|
||||||
'date': today,
|
|
||||||
'activities': _actCtrl.text.trim(),
|
|
||||||
'notes': _notesCtrl.text.trim(),
|
|
||||||
'institution_notes': _instNotesCtrl.text.trim(),
|
|
||||||
}).select('id').single();
|
|
||||||
diaryId = res['id'] as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Sono
|
|
||||||
await sb.from('sleep_records').upsert({
|
|
||||||
'child_id': _childId,
|
|
||||||
'diary_id': diaryId,
|
|
||||||
'date': today,
|
|
||||||
'morning': _sleepMorning,
|
|
||||||
'afternoon': _sleepAfternoon,
|
|
||||||
}, onConflict: 'child_id,date');
|
|
||||||
|
|
||||||
// 3. Alimentação
|
|
||||||
if (_breakfast.isNotEmpty || _lunch.isNotEmpty || _snackMeal.isNotEmpty) {
|
|
||||||
await sb.from('meal_records').upsert({
|
|
||||||
'child_id': _childId,
|
|
||||||
'diary_id': diaryId,
|
|
||||||
'date': today,
|
|
||||||
'breakfast': _breakfast,
|
|
||||||
'lunch': _lunch,
|
|
||||||
'snack': _snackMeal,
|
|
||||||
}, onConflict: 'child_id,date');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Higiene
|
|
||||||
if (_hygieneFreq > 0 || _hygieneState.isNotEmpty) {
|
|
||||||
await sb.from('hygiene_records').upsert({
|
|
||||||
'child_id': _childId,
|
|
||||||
'diary_id': diaryId,
|
|
||||||
'date': today,
|
|
||||||
'frequency': _hygieneFreq,
|
|
||||||
'state': _hygieneState,
|
|
||||||
}, onConflict: 'child_id,date');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
_snack('Diário guardado! ✓', ok: true);
|
|
||||||
await Future.delayed(const Duration(milliseconds: 600));
|
|
||||||
if (mounted) context.pop();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_snack('Erro: $e');
|
|
||||||
} finally {
|
|
||||||
if (mounted) setState(() => _loading = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _snack(String msg, {bool ok = false}) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(msg, style: const TextStyle(color: Colors.white)),
|
|
||||||
backgroundColor: ok ? _green : _red, behavior: SnackBarBehavior.floating,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final today = DateFormat('d MMMM yyyy', 'pt_PT').format(DateTime.now());
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: _bg,
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: _card, elevation: 0,
|
|
||||||
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
||||||
const Text('Diário do Dia', style: TextStyle(color: _blue, fontSize: 16, fontWeight: FontWeight.bold)),
|
|
||||||
Text(today, style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 11)),
|
|
||||||
]),
|
|
||||||
actions: [
|
|
||||||
TextButton.icon(
|
|
||||||
icon: _loading ? const SizedBox(width: 16, height: 16,
|
|
||||||
child: CircularProgressIndicator(color: _blue, strokeWidth: 2))
|
|
||||||
: const Icon(Icons.save_outlined, color: _blue, size: 18),
|
|
||||||
label: const Text('Guardar', style: TextStyle(color: _blue, fontWeight: FontWeight.bold)),
|
|
||||||
onPressed: _loading ? null : _save,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: _loadingChildren
|
|
||||||
? const Center(child: CircularProgressIndicator(color: _blue))
|
|
||||||
: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(children: [
|
|
||||||
|
|
||||||
// ── Seleccionar criança ─────────────────────────
|
|
||||||
_Card(title: '👶 Criança', children: [
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
value: _childId,
|
|
||||||
dropdownColor: _card,
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
decoration: _dec('Selecciona a criança', Icons.child_care),
|
|
||||||
items: _children.map((c) => DropdownMenuItem(
|
|
||||||
value: c.id,
|
|
||||||
child: Text(c.fullName, style: const TextStyle(color: Colors.white)),
|
|
||||||
)).toList(),
|
|
||||||
onChanged: (v) => setState(() => _childId = v),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// ── Actividades ─────────────────────────────────
|
|
||||||
_Card(title: '🎨 Actividades do Dia', children: [
|
|
||||||
TextField(
|
|
||||||
controller: _actCtrl, maxLines: 4,
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
|
||||||
decoration: _dec('Descreve as actividades, brincadeiras, aprendizagens...', Icons.edit_note),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// ── Controlo de Sono ────────────────────────────
|
|
||||||
_Card(title: '😴 Controlo de Sono', children: [
|
|
||||||
Row(children: [
|
|
||||||
Expanded(child: _CheckTile(
|
|
||||||
label: 'Manhã', value: _sleepMorning,
|
|
||||||
onChanged: (v) => setState(() => _sleepMorning = v),
|
|
||||||
)),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(child: _CheckTile(
|
|
||||||
label: 'Tarde', value: _sleepAfternoon,
|
|
||||||
onChanged: (v) => setState(() => _sleepAfternoon = v),
|
|
||||||
)),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// ── Alimentação ─────────────────────────────────
|
|
||||||
_Card(title: '🍽️ Alimentação', children: [
|
|
||||||
_MealRow(label: 'Pequeno Almoço', value: _breakfast,
|
|
||||||
onChanged: (v) => setState(() => _breakfast = v)),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_MealRow(label: 'Almoço', value: _lunch,
|
|
||||||
onChanged: (v) => setState(() => _lunch = v)),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_MealRow(label: 'Lanche', value: _snackMeal,
|
|
||||||
onChanged: (v) => setState(() => _snackMeal = v)),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// ── Higiene/Evacuação ───────────────────────────
|
|
||||||
_Card(title: '🚿 Higiene & Evacuação', children: [
|
|
||||||
Row(children: [
|
|
||||||
const Text('Frequência:', style: TextStyle(color: Color(0xFF888888), fontSize: 13)),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => setState(() => _hygieneFreq = (_hygieneFreq - 1).clamp(0, 20)),
|
|
||||||
icon: const Icon(Icons.remove_circle_outline, color: _red),
|
|
||||||
),
|
|
||||||
Text('$_hygieneFreq x',
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => setState(() => _hygieneFreq++),
|
|
||||||
icon: const Icon(Icons.add_circle_outline, color: _green),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
const Text('Estado:', style: TextStyle(color: Color(0xFF888888), fontSize: 13)),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Wrap(spacing: 8, children: _hygieneOpts.map((opt) {
|
|
||||||
final sel = _hygieneState == opt;
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => setState(() => _hygieneState = sel ? '' : opt),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: sel ? _blue.withOpacity(0.2) : Colors.white.withOpacity(0.05),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: sel ? _blue : Colors.white.withOpacity(0.1)),
|
|
||||||
),
|
|
||||||
child: Text(_hygieneLabels[opt]!,
|
|
||||||
style: TextStyle(color: sel ? _blue : Colors.white70, fontSize: 13)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList()),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// ── Notas da Educadora ──────────────────────────
|
|
||||||
_Card(title: '📝 Notas da Educadora', children: [
|
|
||||||
TextField(
|
|
||||||
controller: _notesCtrl, maxLines: 3,
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
|
||||||
decoration: _dec('Observações, comportamento, necessidades especiais...', Icons.notes),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
// ── Notas da Instituição ────────────────────────
|
|
||||||
_Card(title: '🏫 Notas da Instituição', children: [
|
|
||||||
TextField(
|
|
||||||
controller: _instNotesCtrl, maxLines: 3,
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
|
||||||
decoration: _dec('Comunicado para o encarregado de educação...', Icons.business_outlined),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 80),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
|
||||||
backgroundColor: _blue,
|
|
||||||
onPressed: _loading ? null : _save,
|
|
||||||
icon: _loading ? const SizedBox(width: 18, height: 18,
|
|
||||||
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
|
|
||||||
: const Icon(Icons.save, color: Colors.white),
|
|
||||||
label: const Text('Guardar Diário', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDecoration _dec(String hint, IconData icon) => InputDecoration(
|
|
||||||
hintText: hint, hintStyle: const TextStyle(color: Color(0xFF555555), fontSize: 13),
|
|
||||||
prefixIcon: Icon(icon, color: _blue.withOpacity(0.6), size: 18),
|
|
||||||
filled: true, fillColor: Colors.white.withOpacity(0.04),
|
|
||||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12),
|
|
||||||
borderSide: BorderSide(color: Colors.white.withOpacity(0.09))),
|
|
||||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12),
|
|
||||||
borderSide: const BorderSide(color: _blue, width: 1.5)),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Card extends StatelessWidget {
|
|
||||||
final String title; final List<Widget> children;
|
|
||||||
const _Card({required this.title, required this.children});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(color: _card, borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.07))),
|
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
||||||
Text(title, style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
...children,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CheckTile extends StatelessWidget {
|
|
||||||
final String label; final bool value; final ValueChanged<bool> onChanged;
|
|
||||||
const _CheckTile({required this.label, required this.value, required this.onChanged});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => GestureDetector(
|
|
||||||
onTap: () => onChanged(!value),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: value ? _blue.withOpacity(0.12) : Colors.white.withOpacity(0.04),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: value ? _blue.withOpacity(0.4) : Colors.white.withOpacity(0.09)),
|
|
||||||
),
|
|
||||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
|
||||||
Icon(value ? Icons.check_circle : Icons.circle_outlined,
|
|
||||||
color: value ? _blue : Colors.white38, size: 18),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(label, style: TextStyle(color: value ? _blue : Colors.white60, fontWeight: FontWeight.w500)),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MealRow extends StatelessWidget {
|
|
||||||
final String label, value; final ValueChanged<String> onChanged;
|
|
||||||
const _MealRow({required this.label, required this.value, required this.onChanged});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
||||||
Text(label, style: const TextStyle(color: Color(0xFF888888), fontSize: 12)),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Row(children: _mealOpts.map((opt) {
|
|
||||||
final sel = value == opt;
|
|
||||||
Color c = opt == 'bem' ? const Color(0xFF2ECC71) : opt == 'pouco' ? const Color(0xFFFFB300) : const Color(0xFFE74C3C);
|
|
||||||
return Expanded(child: GestureDetector(
|
|
||||||
onTap: () => onChanged(sel ? '' : opt),
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(right: 6),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: sel ? c.withOpacity(0.15) : Colors.white.withOpacity(0.04),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: Border.all(color: sel ? c.withOpacity(0.5) : Colors.white.withOpacity(0.08)),
|
|
||||||
),
|
|
||||||
child: Text(_mealLabels[opt]!, textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(color: sel ? c : Colors.white54, fontSize: 11, fontWeight: FontWeight.w500)),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}).toList()),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue