feat(#132): upload photo à la création d'enfant (admin).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-17 18:07:47 +02:00
co-authored by Cursor
parent 0ec3410457
commit 26e9738ec7
5 changed files with 235 additions and 21 deletions
@@ -1,4 +1,7 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
import 'package:p_tits_pas/models/enfant_admin_model.dart';
import 'package:p_tits_pas/services/api/api_config.dart';
@@ -62,6 +65,10 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
/// Famille choisie en mode création (#132).
AdminFamilleFoyer? _selectedFamily;
/// Photo locale (création) — upload multipart `photo`.
Uint8List? _photoBytes;
String? _photoFilename;
static const double _modalWidth = 930;
static const double _mainRowHeight = 380;
static const double _placementHeight = 96;
@@ -370,11 +377,24 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
);
return;
}
final hasPhoto = _photoBytes != null && _photoBytes!.isNotEmpty;
if (hasPhoto && !_consentPhoto) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Activez le consentement photo pour enregistrer une photo',
),
),
);
return;
}
setState(() => _saving = true);
try {
await UserService.createEnfant(
parentUserId: family.pivotParentUserId,
photoBytes: hasPhoto ? _photoBytes : null,
photoFilename: _photoFilename,
body: {
'first_name': prenom,
'last_name': nom,
@@ -383,8 +403,8 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
if (_isUnborn)
if (_dateToIso(_dueCtrl.text) != null)
'due_date': _dateToIso(_dueCtrl.text)
else if (_dateToIso(_birthCtrl.text) != null)
'birth_date': _dateToIso(_birthCtrl.text),
else if (_dateToIso(_birthCtrl.text) != null)
'birth_date': _dateToIso(_birthCtrl.text),
'consent_photo': _consentPhoto,
'is_multiple': _isMultiple,
},
@@ -727,6 +747,43 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
);
}
Future<void> _pickPhoto() async {
if (_busy || !widget.isCreating) return;
try {
final picked = await ImagePicker().pickImage(
source: ImageSource.gallery,
imageQuality: 75,
maxWidth: 1200,
maxHeight: 1200,
);
if (picked == null) return;
final bytes = await picked.readAsBytes();
if (bytes.isEmpty) return;
final name = picked.name.trim();
if (!mounted) return;
setState(() {
_photoBytes = bytes;
_photoFilename = name.isNotEmpty ? name : 'photo.jpg';
_consentPhoto = true;
_dirty = true;
});
} catch (_) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Impossible de charger la photo')),
);
}
}
void _clearPhoto() {
if (_busy || !widget.isCreating) return;
setState(() {
_photoBytes = null;
_photoFilename = null;
_dirty = true;
});
}
Widget _mainRow() {
return LayoutBuilder(
builder: (context, c) {
@@ -749,7 +806,20 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
children: [
Expanded(
child: AdminAmPhotoFrame(
photoUrl: widget.enfant?.photoUrl,
photoUrl: widget.isCreating
? null
: widget.enfant?.photoUrl,
imageBytes: widget.isCreating ? _photoBytes : null,
onTap: widget.isCreating && !_busy ? _pickPhoto : null,
onClear: widget.isCreating &&
!_busy &&
_photoBytes != null &&
_photoBytes!.isNotEmpty
? _clearPhoto
: null,
emptyLabel: widget.isCreating
? 'Cliquer pour ajouter'
: 'Aucune photo',
),
),
const SizedBox(height: 8),