Compare commits
No commits in common. "04ab6e0a7e2425a1dd4a7a5af1416b734fabb4ba" and "7d97de308643001c6111c95b2b16d580a6db4fb8" have entirely different histories.
04ab6e0a7e
...
7d97de3086
@ -1,138 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/assistant_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/child_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/contract_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/conversation_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/event_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/notification_model.dart';
|
|
||||||
import 'package:p_tits_pas/services/dashboardService.dart';
|
|
||||||
|
|
||||||
class ParentDashboardController extends ChangeNotifier {
|
|
||||||
final DashboardService _dashboardService;
|
|
||||||
|
|
||||||
ParentDashboardController(this._dashboardService);
|
|
||||||
|
|
||||||
// État des données
|
|
||||||
List<ChildModel> _children = [];
|
|
||||||
String? _selectedChildId;
|
|
||||||
AssistantModel? _selectedAssistant;
|
|
||||||
List<EventModel> _upcomingEvents = [];
|
|
||||||
List<ContractModel> _contracts = [];
|
|
||||||
List<ConversationModel> _conversations = [];
|
|
||||||
List<NotificationModel> _notifications = [];
|
|
||||||
|
|
||||||
// État de chargement
|
|
||||||
bool _isLoading = false;
|
|
||||||
String? _error;
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
List<ChildModel> get children => _children;
|
|
||||||
String? get selectedChildId => _selectedChildId;
|
|
||||||
ChildModel? get selectedChild => _children.where((c) => c.id == _selectedChildId).firstOrNull;
|
|
||||||
AssistantModel? get selectedAssistant => _selectedAssistant;
|
|
||||||
List<EventModel> get upcomingEvents => _upcomingEvents;
|
|
||||||
List<ContractModel> get contracts => _contracts;
|
|
||||||
List<ConversationModel> get conversations => _conversations;
|
|
||||||
List<NotificationModel> get notifications => _notifications;
|
|
||||||
bool get isLoading => _isLoading;
|
|
||||||
String? get error => _error;
|
|
||||||
|
|
||||||
// Initialisation du dashboard
|
|
||||||
Future<void> initDashboard() async {
|
|
||||||
_isLoading = true;
|
|
||||||
_error = null;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Future.wait([
|
|
||||||
_loadChildren(),
|
|
||||||
_loadUpcomingEvents(),
|
|
||||||
_loadContracts(),
|
|
||||||
_loadConversations(),
|
|
||||||
_loadNotifications(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Sélectionner le premier enfant par défaut
|
|
||||||
if (_children.isNotEmpty && _selectedChildId == null) {
|
|
||||||
await selectChild(_children.first.id);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_error = 'Erreur lors du chargement du tableau de bord: $e';
|
|
||||||
} finally {
|
|
||||||
_isLoading = false;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sélection d'un enfant
|
|
||||||
Future<void> selectChild(String childId) async {
|
|
||||||
_selectedChildId = childId;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
// Charger les données spécifiques à cet enfant
|
|
||||||
await _loadChildSpecificData(childId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Afficher le modal d'ajout d'enfant
|
|
||||||
void showAddChildModal() {
|
|
||||||
// Logique pour ouvrir le modal d'ajout d'enfant
|
|
||||||
// Sera implémentée dans le ticket FRONT-09
|
|
||||||
}
|
|
||||||
|
|
||||||
// Méthodes privées de chargement des données
|
|
||||||
Future<void> _loadChildren() async {
|
|
||||||
_children = await _dashboardService.getChildren();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadChildSpecificData(String childId) async {
|
|
||||||
try {
|
|
||||||
// Charger l'assistante maternelle associée à cet enfant
|
|
||||||
_selectedAssistant = await _dashboardService.getAssistantForChild(childId);
|
|
||||||
|
|
||||||
// Filtrer les événements et contrats pour cet enfant
|
|
||||||
_upcomingEvents = await _dashboardService.getEventsForChild(childId);
|
|
||||||
_contracts = await _dashboardService.getContractsForChild(childId);
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
} catch (e) {
|
|
||||||
_error = 'Erreur lors du chargement des données pour l\'enfant: $e';
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadUpcomingEvents() async {
|
|
||||||
_upcomingEvents = await _dashboardService.getUpcomingEvents();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadContracts() async {
|
|
||||||
_contracts = await _dashboardService.getContracts();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadConversations() async {
|
|
||||||
_conversations = await _dashboardService.getConversations();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadNotifications() async {
|
|
||||||
_notifications = await _dashboardService.getNotifications();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Méthodes d'action
|
|
||||||
Future<void> markNotificationAsRead(String notificationId) async {
|
|
||||||
try {
|
|
||||||
await _dashboardService.markNotificationAsRead(notificationId);
|
|
||||||
await _loadNotifications(); // Recharger les notifications
|
|
||||||
} catch (e) {
|
|
||||||
_error = 'Erreur lors du marquage de la notification: $e';
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refreshDashboard() async {
|
|
||||||
await initDashboard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart'; // Import pour la localisation
|
||||||
|
// import 'package:provider/provider.dart'; // Supprimer Provider
|
||||||
import 'navigation/app_router.dart';
|
import 'navigation/app_router.dart';
|
||||||
|
// import 'theme/app_theme.dart'; // Supprimer AppTheme
|
||||||
|
// import 'theme/theme_provider.dart'; // Supprimer ThemeProvider
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp()); // Exécution simple
|
runApp(const MyApp()); // Exécution simple
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
class AssistantModel {
|
|
||||||
final String id;
|
|
||||||
final String firstName;
|
|
||||||
final String lastName;
|
|
||||||
final String? photoUrl;
|
|
||||||
final double hourlyRate;
|
|
||||||
final double dailyFees;
|
|
||||||
final AssistantStatus status;
|
|
||||||
final String? address;
|
|
||||||
final String? phone;
|
|
||||||
final String? email;
|
|
||||||
|
|
||||||
AssistantModel({
|
|
||||||
required this.id,
|
|
||||||
required this.firstName,
|
|
||||||
required this.lastName,
|
|
||||||
this.photoUrl,
|
|
||||||
required this.hourlyRate,
|
|
||||||
required this.dailyFees,
|
|
||||||
required this.status,
|
|
||||||
this.address,
|
|
||||||
this.phone,
|
|
||||||
this.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory AssistantModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return AssistantModel(
|
|
||||||
id: json['id'],
|
|
||||||
firstName: json['firstName'],
|
|
||||||
lastName: json['lastName'],
|
|
||||||
photoUrl: json['photoUrl'],
|
|
||||||
hourlyRate: json['hourlyRate'].toDouble(),
|
|
||||||
dailyFees: json['dailyFees'].toDouble(),
|
|
||||||
status: AssistantStatus.values.byName(json['status']),
|
|
||||||
address: json['address'],
|
|
||||||
phone: json['phone'],
|
|
||||||
email: json['email'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String get fullName => '$firstName $lastName';
|
|
||||||
String get hourlyRateFormatted => '${hourlyRate.toStringAsFixed(2)} €/h';
|
|
||||||
String get dailyFeesFormatted => '${dailyFees.toStringAsFixed(2)} €/jour';
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AssistantStatus {
|
|
||||||
available,
|
|
||||||
busy,
|
|
||||||
onHoliday,
|
|
||||||
unavailable,
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
class ChildModel {
|
|
||||||
final String id;
|
|
||||||
final String firstName;
|
|
||||||
final String? lastName;
|
|
||||||
final String? photoUrl;
|
|
||||||
final DateTime birthDate;
|
|
||||||
final ChildStatus status;
|
|
||||||
final String? assistantId;
|
|
||||||
|
|
||||||
ChildModel({
|
|
||||||
required this.id,
|
|
||||||
required this.firstName,
|
|
||||||
this.lastName,
|
|
||||||
this.photoUrl,
|
|
||||||
required this.birthDate,
|
|
||||||
required this.status,
|
|
||||||
this.assistantId,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ChildModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return ChildModel(
|
|
||||||
id: json['id'],
|
|
||||||
firstName: json['firstName'],
|
|
||||||
lastName: json['lastName'],
|
|
||||||
photoUrl: json['photoUrl'],
|
|
||||||
birthDate: DateTime.parse(json['birthDate']),
|
|
||||||
status: ChildStatus.values.byName(json['status']),
|
|
||||||
assistantId: json['assistantId'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'firstName': firstName,
|
|
||||||
'lastName': lastName,
|
|
||||||
'photoUrl': photoUrl,
|
|
||||||
'birthDate': birthDate.toIso8601String(),
|
|
||||||
'status': status.name,
|
|
||||||
'assistantId': assistantId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
String get fullName => lastName != null ? '$firstName $lastName' : firstName;
|
|
||||||
|
|
||||||
int get ageInMonths {
|
|
||||||
final now = DateTime.now();
|
|
||||||
return (now.year - birthDate.year) * 12 + (now.month - birthDate.month);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ChildStatus {
|
|
||||||
withAssistant, // En garde chez l'assistante
|
|
||||||
available, // Disponible
|
|
||||||
onHoliday, // En vacances
|
|
||||||
sick, // Malade
|
|
||||||
searching, // Recherche d'assistante
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
class ContractModel {
|
|
||||||
final String id;
|
|
||||||
final String childId;
|
|
||||||
final String assistantId;
|
|
||||||
final ContractStatus status;
|
|
||||||
final DateTime startDate;
|
|
||||||
final DateTime? endDate;
|
|
||||||
final double hourlyRate;
|
|
||||||
final Map<String, dynamic>? terms;
|
|
||||||
final DateTime createdAt;
|
|
||||||
final DateTime? signedAt;
|
|
||||||
|
|
||||||
ContractModel({
|
|
||||||
required this.id,
|
|
||||||
required this.childId,
|
|
||||||
required this.assistantId,
|
|
||||||
required this.status,
|
|
||||||
required this.startDate,
|
|
||||||
this.endDate,
|
|
||||||
required this.hourlyRate,
|
|
||||||
this.terms,
|
|
||||||
required this.createdAt,
|
|
||||||
this.signedAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ContractModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return ContractModel(
|
|
||||||
id: json['id'],
|
|
||||||
childId: json['childId'],
|
|
||||||
assistantId: json['assistantId'],
|
|
||||||
status: ContractStatus.values.byName(json['status']),
|
|
||||||
startDate: DateTime.parse(json['startDate']),
|
|
||||||
endDate: json['endDate'] != null ? DateTime.parse(json['endDate']) : null,
|
|
||||||
hourlyRate: json['hourlyRate'].toDouble(),
|
|
||||||
terms: json['terms'],
|
|
||||||
createdAt: DateTime.parse(json['createdAt']),
|
|
||||||
signedAt: json['signedAt'] != null ? DateTime.parse(json['signedAt']) : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isActive => status == ContractStatus.active;
|
|
||||||
bool get needsSignature => status == ContractStatus.draft;
|
|
||||||
String get statusLabel {
|
|
||||||
switch (status) {
|
|
||||||
case ContractStatus.draft:
|
|
||||||
return 'Brouillon';
|
|
||||||
case ContractStatus.pending:
|
|
||||||
return 'En attente de validation';
|
|
||||||
case ContractStatus.active:
|
|
||||||
return 'En cours';
|
|
||||||
case ContractStatus.ended:
|
|
||||||
return 'Terminé';
|
|
||||||
case ContractStatus.cancelled:
|
|
||||||
return 'Annulé';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ContractStatus {
|
|
||||||
draft,
|
|
||||||
pending,
|
|
||||||
active,
|
|
||||||
ended,
|
|
||||||
cancelled,
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
class ConversationModel {
|
|
||||||
final String id;
|
|
||||||
final String title;
|
|
||||||
final List<String> participantIds;
|
|
||||||
final List<MessageModel> messages;
|
|
||||||
final DateTime lastMessageAt;
|
|
||||||
final int unreadCount;
|
|
||||||
final String? childId;
|
|
||||||
|
|
||||||
ConversationModel({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.participantIds,
|
|
||||||
required this.messages,
|
|
||||||
required this.lastMessageAt,
|
|
||||||
this.unreadCount = 0,
|
|
||||||
this.childId,
|
|
||||||
});
|
|
||||||
|
|
||||||
MessageModel? get lastMessage => messages.isNotEmpty ? messages.last : null;
|
|
||||||
bool get hasUnreadMessages => unreadCount > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MessageModel {
|
|
||||||
final String id;
|
|
||||||
final String content;
|
|
||||||
final String senderId;
|
|
||||||
final DateTime sentAt;
|
|
||||||
final bool isFromAI;
|
|
||||||
final MessageStatus status;
|
|
||||||
|
|
||||||
MessageModel({
|
|
||||||
required this.id,
|
|
||||||
required this.content,
|
|
||||||
required this.senderId,
|
|
||||||
required this.sentAt,
|
|
||||||
this.isFromAI = false,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MessageStatus {
|
|
||||||
sent,
|
|
||||||
delivered,
|
|
||||||
read,
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
class EventModel {
|
|
||||||
final String id;
|
|
||||||
final String title;
|
|
||||||
final String? description;
|
|
||||||
final DateTime startDate;
|
|
||||||
final DateTime? endDate;
|
|
||||||
final EventType type;
|
|
||||||
final EventStatus status;
|
|
||||||
final String? childId;
|
|
||||||
final String? assistantId;
|
|
||||||
final String? createdBy;
|
|
||||||
|
|
||||||
EventModel({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
this.description,
|
|
||||||
required this.startDate,
|
|
||||||
this.endDate,
|
|
||||||
required this.type,
|
|
||||||
required this.status,
|
|
||||||
this.childId,
|
|
||||||
this.assistantId,
|
|
||||||
this.createdBy,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory EventModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return EventModel(
|
|
||||||
id: json['id'],
|
|
||||||
title: json['title'],
|
|
||||||
description: json['description'],
|
|
||||||
startDate: DateTime.parse(json['startDate']),
|
|
||||||
endDate: json['endDate'] != null ? DateTime.parse(json['endDate']) : null,
|
|
||||||
type: EventType.values.byName(json['type']),
|
|
||||||
status: EventStatus.values.byName(json['status']),
|
|
||||||
childId: json['childId'],
|
|
||||||
assistantId: json['assistantId'],
|
|
||||||
createdBy: json['createdBy'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isMultiDay => endDate != null && !isSameDay(startDate, endDate!);
|
|
||||||
bool get isPending => status == EventStatus.pending;
|
|
||||||
bool get needsConfirmation => isPending && createdBy != 'current_user';
|
|
||||||
|
|
||||||
static bool isSameDay(DateTime date1, DateTime date2) {
|
|
||||||
return date1.year == date2.year &&
|
|
||||||
date1.month == date2.month &&
|
|
||||||
date1.day == date2.day;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EventType {
|
|
||||||
parentVacation, // Vacances parents
|
|
||||||
childAbsence, // Absence enfant
|
|
||||||
rpeActivity, // Activité RPE
|
|
||||||
assistantVacation, // Congés assistante maternelle
|
|
||||||
sickLeave, // Arrêt maladie
|
|
||||||
personalNote, // Note personnelle
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EventStatus {
|
|
||||||
confirmed,
|
|
||||||
pending,
|
|
||||||
refused,
|
|
||||||
cancelled,
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
class NotificationModel {
|
|
||||||
final String id;
|
|
||||||
final String title;
|
|
||||||
final String content;
|
|
||||||
final NotificationType type;
|
|
||||||
final DateTime createdAt;
|
|
||||||
final bool isRead;
|
|
||||||
final String? actionUrl;
|
|
||||||
final Map<String, dynamic>? metadata;
|
|
||||||
|
|
||||||
NotificationModel({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.content,
|
|
||||||
required this.type,
|
|
||||||
required this.createdAt,
|
|
||||||
this.isRead = false,
|
|
||||||
this.actionUrl,
|
|
||||||
this.metadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory NotificationModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return NotificationModel(
|
|
||||||
id: json['id'],
|
|
||||||
title: json['title'],
|
|
||||||
content: json['content'],
|
|
||||||
type: NotificationType.values.byName(json['type']),
|
|
||||||
createdAt: DateTime.parse(json['createdAt']),
|
|
||||||
isRead: json['isRead'] ?? false,
|
|
||||||
actionUrl: json['actionUrl'],
|
|
||||||
metadata: json['metadata'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NotificationType {
|
|
||||||
newEvent, // Nouvel événement
|
|
||||||
fileModified, // Dossier modifié
|
|
||||||
contractPending, // Contrat en attente
|
|
||||||
paymentPending, // Paiement en attente
|
|
||||||
unreadMessage, // Message non lu
|
|
||||||
}
|
|
||||||
@ -4,8 +4,6 @@ import 'package:p_tits_pas/screens/auth/am/am_register_step1_sceen.dart';
|
|||||||
import 'package:p_tits_pas/screens/auth/am/am_register_step2_sceen.dart';
|
import 'package:p_tits_pas/screens/auth/am/am_register_step2_sceen.dart';
|
||||||
import 'package:p_tits_pas/screens/auth/am/am_register_step3_sceen.dart';
|
import 'package:p_tits_pas/screens/auth/am/am_register_step3_sceen.dart';
|
||||||
import 'package:p_tits_pas/screens/auth/am/am_register_step4_sceen.dart';
|
import 'package:p_tits_pas/screens/auth/am/am_register_step4_sceen.dart';
|
||||||
import 'package:p_tits_pas/screens/home/parent_screen/ParentDashboardScreen.dart';
|
|
||||||
import 'package:p_tits_pas/screens/home/parent_screen/find_nanny.dart';
|
|
||||||
import 'package:p_tits_pas/screens/legal/legal_page.dart';
|
import 'package:p_tits_pas/screens/legal/legal_page.dart';
|
||||||
import 'package:p_tits_pas/screens/legal/privacy_page.dart';
|
import 'package:p_tits_pas/screens/legal/privacy_page.dart';
|
||||||
import '../screens/auth/login_screen.dart';
|
import '../screens/auth/login_screen.dart';
|
||||||
@ -15,6 +13,7 @@ import '../screens/auth/parent/parent_register_step2_screen.dart';
|
|||||||
import '../screens/auth/parent/parent_register_step3_screen.dart';
|
import '../screens/auth/parent/parent_register_step3_screen.dart';
|
||||||
import '../screens/auth/parent/parent_register_step4_screen.dart';
|
import '../screens/auth/parent/parent_register_step4_screen.dart';
|
||||||
import '../screens/auth/parent/parent_register_step5_screen.dart';
|
import '../screens/auth/parent/parent_register_step5_screen.dart';
|
||||||
|
import '../screens/home/home_screen.dart';
|
||||||
import '../models/parent_user_registration_data.dart';
|
import '../models/parent_user_registration_data.dart';
|
||||||
|
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
@ -32,8 +31,7 @@ class AppRouter {
|
|||||||
static const String amRegisterStep2 = '/am-register/step2';
|
static const String amRegisterStep2 = '/am-register/step2';
|
||||||
static const String amRegisterStep3 = '/am-register/step3';
|
static const String amRegisterStep3 = '/am-register/step3';
|
||||||
static const String amRegisterStep4 = '/am-register/step4';
|
static const String amRegisterStep4 = '/am-register/step4';
|
||||||
static const String parentDashboard = '/parent-dashboard';
|
static const String home = '/home';
|
||||||
static const String findNanny = '/find-nanny';
|
|
||||||
|
|
||||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
Widget screen;
|
Widget screen;
|
||||||
@ -61,10 +59,6 @@ class AppRouter {
|
|||||||
screen = const PrivacyPage();
|
screen = const PrivacyPage();
|
||||||
slideTransition = true;
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentRegisterStep1:
|
|
||||||
screen = ParentRegisterStep1Screen();
|
|
||||||
slideTransition = true;
|
|
||||||
break;
|
|
||||||
case parentRegisterStep2:
|
case parentRegisterStep2:
|
||||||
if (args is UserRegistrationData) {
|
if (args is UserRegistrationData) {
|
||||||
screen = ParentRegisterStep2Screen(registrationData: args);
|
screen = ParentRegisterStep2Screen(registrationData: args);
|
||||||
@ -125,11 +119,8 @@ class AppRouter {
|
|||||||
}
|
}
|
||||||
slideTransition = true;
|
slideTransition = true;
|
||||||
break;
|
break;
|
||||||
case parentDashboard:
|
case home:
|
||||||
screen = const ParentDashboardScreen();
|
screen = const HomeScreen();
|
||||||
break;
|
|
||||||
case findNanny:
|
|
||||||
screen = const FindNannyScreen();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
screen = Scaffold(
|
screen = Scaffold(
|
||||||
|
|||||||
@ -1,242 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/controllers/parent_dashboard_controller.dart';
|
|
||||||
import 'package:p_tits_pas/services/dashboardService.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/app_footer.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/app_layout.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/children_sidebar.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/dashboard_app_bar.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/wid_dashbord.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/main_content_area.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/messaging_sidebar.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class ParentDashboardScreen extends StatefulWidget {
|
|
||||||
const ParentDashboardScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ParentDashboardScreen> createState() => _ParentDashboardScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ParentDashboardScreenState extends State<ParentDashboardScreen> {
|
|
||||||
int selectedIndex = 0;
|
|
||||||
|
|
||||||
void onTabChange(int index) {
|
|
||||||
setState(() {
|
|
||||||
selectedIndex = index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
// Initialiser les données du dashboard
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
context.read<ParentDashboardController>().initDashboard();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _getBody() {
|
|
||||||
switch (selectedIndex) {
|
|
||||||
case 0:
|
|
||||||
return Dashbord_body();
|
|
||||||
case 1:
|
|
||||||
return const Center(child: Text("🔍 Trouver une nounou"));
|
|
||||||
case 2:
|
|
||||||
return const Center(child: Text("⚙️ Paramètres"));
|
|
||||||
default:
|
|
||||||
return const Center(child: Text("Page non trouvée"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ChangeNotifierProvider(
|
|
||||||
create: (context) => ParentDashboardController(DashboardService())..initDashboard(),
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: PreferredSize(preferredSize: const Size.fromHeight(60.0),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(color: Colors.grey.shade300),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DashboardAppBar(
|
|
||||||
selectedIndex: selectedIndex,
|
|
||||||
onTabChange: onTabChange,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
Expanded (child: _getBody(),
|
|
||||||
),
|
|
||||||
const AppFooter(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// body: _buildResponsiveBody(context, controller),
|
|
||||||
// footer: const AppFooter(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildResponsiveBody(BuildContext context, ParentDashboardController controller) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
if (constraints.maxWidth < 768) {
|
|
||||||
// Layout mobile : colonnes empilées
|
|
||||||
return _buildMobileLayout(controller);
|
|
||||||
} else if (constraints.maxWidth < 1024) {
|
|
||||||
// Layout tablette : 2 colonnes
|
|
||||||
return _buildTabletLayout(controller);
|
|
||||||
} else {
|
|
||||||
// Layout desktop : 3 colonnes
|
|
||||||
return _buildDesktopLayout(controller);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDesktopLayout(ParentDashboardController controller) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Sidebar gauche - Enfants
|
|
||||||
SizedBox(
|
|
||||||
width: 280,
|
|
||||||
child: ChildrenSidebar(
|
|
||||||
children: controller.children,
|
|
||||||
selectedChildId: controller.selectedChildId,
|
|
||||||
onChildSelected: controller.selectChild,
|
|
||||||
onAddChild: controller.showAddChildModal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Contenu central
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: MainContentArea(
|
|
||||||
selectedChild: controller.selectedChild,
|
|
||||||
selectedAssistant: controller.selectedAssistant,
|
|
||||||
events: controller.upcomingEvents,
|
|
||||||
contracts: controller.contracts,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Sidebar droite - Messagerie
|
|
||||||
SizedBox(
|
|
||||||
width: 320,
|
|
||||||
child: MessagingSidebar(
|
|
||||||
conversations: controller.conversations,
|
|
||||||
notifications: controller.notifications,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTabletLayout(ParentDashboardController controller) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
// Sidebar enfants plus étroite
|
|
||||||
SizedBox(
|
|
||||||
width: 240,
|
|
||||||
child: ChildrenSidebar(
|
|
||||||
children: controller.children,
|
|
||||||
selectedChildId: controller.selectedChildId,
|
|
||||||
onChildSelected: controller.selectChild,
|
|
||||||
onAddChild: controller.showAddChildModal,
|
|
||||||
isCompact: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Contenu principal avec messagerie intégrée
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: MainContentArea(
|
|
||||||
selectedChild: controller.selectedChild,
|
|
||||||
selectedAssistant: controller.selectedAssistant,
|
|
||||||
events: controller.upcomingEvents,
|
|
||||||
contracts: controller.contracts,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: MessagingSidebar(
|
|
||||||
conversations: controller.conversations,
|
|
||||||
notifications: controller.notifications,
|
|
||||||
isCompact: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMobileLayout(ParentDashboardController controller) {
|
|
||||||
return DefaultTabController(
|
|
||||||
length: 4,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// Navigation par onglets sur mobile
|
|
||||||
Container(
|
|
||||||
color: Theme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
child: const TabBar(
|
|
||||||
isScrollable: true,
|
|
||||||
tabs: [
|
|
||||||
Tab(text: 'Enfants', icon: Icon(Icons.child_care)),
|
|
||||||
Tab(text: 'Planning', icon: Icon(Icons.calendar_month)),
|
|
||||||
Tab(text: 'Contrats', icon: Icon(Icons.description)),
|
|
||||||
Tab(text: 'Messages', icon: Icon(Icons.message)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
children: [
|
|
||||||
// Onglet Enfants
|
|
||||||
ChildrenSidebar(
|
|
||||||
children: controller.children,
|
|
||||||
selectedChildId: controller.selectedChildId,
|
|
||||||
onChildSelected: controller.selectChild,
|
|
||||||
onAddChild: controller.showAddChildModal,
|
|
||||||
isMobile: true,
|
|
||||||
),
|
|
||||||
|
|
||||||
// Onglet Planning
|
|
||||||
MainContentArea(
|
|
||||||
selectedChild: controller.selectedChild,
|
|
||||||
selectedAssistant: controller.selectedAssistant,
|
|
||||||
events: controller.upcomingEvents,
|
|
||||||
contracts: controller.contracts,
|
|
||||||
showOnlyCalendar: true,
|
|
||||||
),
|
|
||||||
|
|
||||||
// Onglet Contrats
|
|
||||||
MainContentArea(
|
|
||||||
selectedChild: controller.selectedChild,
|
|
||||||
selectedAssistant: controller.selectedAssistant,
|
|
||||||
events: controller.upcomingEvents,
|
|
||||||
contracts: controller.contracts,
|
|
||||||
showOnlyContracts: true,
|
|
||||||
),
|
|
||||||
|
|
||||||
// Onglet Messages
|
|
||||||
MessagingSidebar(
|
|
||||||
conversations: controller.conversations,
|
|
||||||
notifications: controller.notifications,
|
|
||||||
isMobile: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class FindNannyScreen extends StatelessWidget {
|
|
||||||
const FindNannyScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text("Trouver une nounou"),
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: const Text("Contenu de la page Trouver une nounou"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,202 +0,0 @@
|
|||||||
import 'package:p_tits_pas/models/m_dashbord/assistant_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/child_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/contract_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/conversation_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/event_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/notification_model.dart';
|
|
||||||
|
|
||||||
class DashboardService {
|
|
||||||
// URL de base de l'API
|
|
||||||
static const String _baseUrl = 'YOUR_API_BASE_URL';
|
|
||||||
|
|
||||||
// Récupérer la liste des enfants
|
|
||||||
Future<List<ChildModel>> getChildren() async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
// Exemple de mock data pour le développement
|
|
||||||
return [
|
|
||||||
ChildModel(
|
|
||||||
id: '1',
|
|
||||||
firstName: 'Emma',
|
|
||||||
birthDate: DateTime(2020, 5, 15),
|
|
||||||
photoUrl: 'assets/images/child1.jpg',
|
|
||||||
status: ChildStatus.onHoliday,
|
|
||||||
),
|
|
||||||
ChildModel(
|
|
||||||
id: '2',
|
|
||||||
firstName: 'Lucas',
|
|
||||||
birthDate: DateTime(2021, 3, 10),
|
|
||||||
photoUrl: 'assets/images/child2.jpg',
|
|
||||||
status: ChildStatus.searching,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des enfants: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer l'assistante maternelle pour un enfant
|
|
||||||
Future<AssistantModel> getAssistantForChild(String childId) async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return AssistantModel(
|
|
||||||
id: 'am1',
|
|
||||||
firstName: 'Marie',
|
|
||||||
lastName: 'Dupont',
|
|
||||||
hourlyRate: 10.0,
|
|
||||||
dailyFees: 80.0,
|
|
||||||
status: AssistantStatus.available,
|
|
||||||
photoUrl: 'assets/images/assistant1.jpg',
|
|
||||||
address: '123 rue des Lilas',
|
|
||||||
phone: '0123456789',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération de l\'assistante: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer les événements pour un enfant
|
|
||||||
Future<List<EventModel>> getEventsForChild(String childId) async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return [
|
|
||||||
EventModel(
|
|
||||||
id: 'evt1',
|
|
||||||
title: 'Rendez-vous médical',
|
|
||||||
startDate: DateTime.now().add(const Duration(days: 2)),
|
|
||||||
type: EventType.parentVacation,
|
|
||||||
status: EventStatus.pending,
|
|
||||||
description: 'Visite de routine',
|
|
||||||
childId: childId,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des événements: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer tous les événements à venir
|
|
||||||
Future<List<EventModel>> getUpcomingEvents() async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return [
|
|
||||||
EventModel(
|
|
||||||
id: 'evt1',
|
|
||||||
title: 'Activité peinture',
|
|
||||||
startDate: DateTime.now().add(const Duration(days: 1)),
|
|
||||||
endDate: DateTime.now().add(const Duration(days: 1, hours: 2)),
|
|
||||||
type: EventType.parentVacation,
|
|
||||||
status: EventStatus.pending,
|
|
||||||
description: 'Atelier créatif',
|
|
||||||
childId: '1',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des événements: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer les contrats
|
|
||||||
Future<List<ContractModel>> getContracts() async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return [
|
|
||||||
ContractModel(
|
|
||||||
id: 'contract1',
|
|
||||||
childId: '1',
|
|
||||||
assistantId: 'am1',
|
|
||||||
startDate: DateTime(2023, 9, 1),
|
|
||||||
endDate: DateTime(2024, 8, 31),
|
|
||||||
status: ContractStatus.pending,
|
|
||||||
hourlyRate: 10.0,
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des contrats: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer les contrats pour un enfant spécifique
|
|
||||||
Future<List<ContractModel>> getContractsForChild(String childId) async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return [
|
|
||||||
ContractModel(
|
|
||||||
id: 'contract1',
|
|
||||||
childId: childId,
|
|
||||||
assistantId: 'am1',
|
|
||||||
startDate: DateTime(2023, 9, 1),
|
|
||||||
endDate: DateTime(2024, 8, 31),
|
|
||||||
status: ContractStatus.active,
|
|
||||||
hourlyRate: 10.0,
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des contrats: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer les conversations
|
|
||||||
Future<List<ConversationModel>> getConversations() async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return [
|
|
||||||
ConversationModel(
|
|
||||||
id: 'conv1',
|
|
||||||
title: 'Conversation avec Marie Dupont',
|
|
||||||
participantIds: ['am1'],
|
|
||||||
messages: [
|
|
||||||
MessageModel(
|
|
||||||
id: 'msg1',
|
|
||||||
content: 'Bonjour, comment ça va ?',
|
|
||||||
senderId: 'am1',
|
|
||||||
sentAt: DateTime.now().subtract(const Duration(hours: 2)),
|
|
||||||
status: MessageStatus.read,
|
|
||||||
),
|
|
||||||
MessageModel(
|
|
||||||
id: 'msg2',
|
|
||||||
content: 'Tout va bien, merci !',
|
|
||||||
senderId: 'parent1',
|
|
||||||
sentAt: DateTime.now().subtract(const Duration(hours: 1, minutes: 30)),
|
|
||||||
status: MessageStatus.read,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
lastMessageAt: DateTime.now().subtract(const Duration(hours: 2)),
|
|
||||||
unreadCount: 2,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des conversations: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer les notifications
|
|
||||||
Future<List<NotificationModel>> getNotifications() async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
return [
|
|
||||||
NotificationModel(
|
|
||||||
id: 'notif1',
|
|
||||||
title: 'Nouveau message',
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
isRead: false,
|
|
||||||
type: NotificationType.contractPending,
|
|
||||||
content: 'Votre contrat est en attente',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des notifications: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marquer une notification comme lue
|
|
||||||
Future<void> markNotificationAsRead(String notificationId) async {
|
|
||||||
try {
|
|
||||||
// TODO: Implémenter l'appel API
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors du marquage de la notification: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,209 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/child_model.dart';
|
|
||||||
import 'package:p_tits_pas/services/bug_report_service.dart';
|
|
||||||
|
|
||||||
class AppFooter extends StatelessWidget {
|
|
||||||
const AppFooter({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
// color: Colors.white,
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(color: Colors.grey.shade300),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
if (constraints.maxWidth < 768) {
|
|
||||||
return _buildMobileFooter(context);
|
|
||||||
} else {
|
|
||||||
return _buildDesktopFooter(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDesktopFooter(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_buildFooterLink(context, 'Contact support', () => _handleContactSupport(context)),
|
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
|
||||||
// _buildFooterDivider(),
|
|
||||||
_buildFooterLink(context, 'Signaler un bug', () => _handleReportBug(context)),
|
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
|
||||||
// _buildFooterDivider(),
|
|
||||||
_buildFooterLink(context, 'Mentions légales', () => _handleLegalNotices(context)),
|
|
||||||
// _buildFooterDivider(),
|
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
|
||||||
_buildFooterLink(context, 'Politique de confidentialité', () => _handlePrivacyPolicy(context)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMobileFooter(BuildContext context) {
|
|
||||||
return PopupMenuButton<String>(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: const [
|
|
||||||
Icon(Icons.info_outline, size: 20),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('Informations'),
|
|
||||||
Icon(Icons.keyboard_arrow_down),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
const PopupMenuItem(value: 'support', child: Text('Contact support')),
|
|
||||||
const PopupMenuItem(value: 'bug', child: Text('Signaler un bug')),
|
|
||||||
const PopupMenuItem(value: 'legal', child: Text('Mentions légales')),
|
|
||||||
const PopupMenuItem(value: 'privacy', child: Text('Politique de confidentialité')),
|
|
||||||
],
|
|
||||||
onSelected: (value) {
|
|
||||||
switch (value) {
|
|
||||||
case 'support':
|
|
||||||
_handleContactSupport(context);
|
|
||||||
break;
|
|
||||||
case 'bug':
|
|
||||||
_handleReportBug(context);
|
|
||||||
break;
|
|
||||||
case 'legal':
|
|
||||||
_handleLegalNotices(context);
|
|
||||||
break;
|
|
||||||
case 'privacy':
|
|
||||||
_handlePrivacyPolicy(context);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFooterLink(BuildContext context, String text, VoidCallback onTap) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleReportBug(BuildContext context) {
|
|
||||||
final TextEditingController controller = TextEditingController();
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text(
|
|
||||||
'Signaler un bug',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
TextField(
|
|
||||||
controller: controller,
|
|
||||||
maxLines: 5,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Décrivez le problème rencontré...',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(
|
|
||||||
'Annuler',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
if (controller.text.trim().isEmpty) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Veuillez décrire le problème',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await BugReportService.sendReport(controller.text);
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Rapport envoyé avec succès',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (context.mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Erreur lors de l\'envoi du rapport',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Envoyer',
|
|
||||||
style: GoogleFonts.merienda(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLegalNotices(BuildContext context) {
|
|
||||||
// Handle legal notices action
|
|
||||||
Navigator.pushNamed(context, '/legal');
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePrivacyPolicy(BuildContext context) {
|
|
||||||
// Handle privacy policy action
|
|
||||||
Navigator.pushNamed(context, '/privacy');
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleContactSupport(BuildContext context) {
|
|
||||||
// Handle contact support action
|
|
||||||
// Navigator.pushNamed(context, '/support');
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFooterDivider() {
|
|
||||||
return Divider(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
thickness: 1,
|
|
||||||
height: 40,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class Childrensidebarwidget extends StatelessWidget{
|
|
||||||
final void Function(String childId) onChildSelected;
|
|
||||||
|
|
||||||
const Childrensidebarwidget({
|
|
||||||
Key? key,
|
|
||||||
required this.onChildSelected,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final children = [
|
|
||||||
{'id': '1', 'name': 'Léna', 'photo': null, 'status': 'Actif'},
|
|
||||||
{'id': '2', 'name': 'Noé', 'photo': null, 'status': 'Inactif'},
|
|
||||||
];
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
color: const Color(0xFFF7F7F7),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// Avatar parent + bouton
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const CircleAvatar(radius: 24, child: Icon(Icons.person)),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
onPressed: () {
|
|
||||||
// Naviguer vers ajout d'enfant
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const Text("Mes enfants", style: TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
// Liste des enfants
|
|
||||||
...children.map((child) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onChildSelected(child['id']!),
|
|
||||||
child: Card(
|
|
||||||
color: child['status'] == 'Actif' ? Colors.teal.shade50 : Colors.white,
|
|
||||||
child: ListTile(
|
|
||||||
leading: const CircleAvatar(child: Icon(Icons.child_care)),
|
|
||||||
title: Text(child['name']!),
|
|
||||||
subtitle: Text(child['status']!),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AppLayout extends StatelessWidget {
|
|
||||||
final PreferredSizeWidget appBar;
|
|
||||||
final Widget body;
|
|
||||||
final Widget? footer;
|
|
||||||
|
|
||||||
const AppLayout({
|
|
||||||
Key? key,
|
|
||||||
required this.appBar,
|
|
||||||
required this.body,
|
|
||||||
this.footer,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color(0xFFF5F7FA),
|
|
||||||
appBar: appBar,
|
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(child: body),
|
|
||||||
if (footer != null) footer!,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,203 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/child_model.dart';
|
|
||||||
|
|
||||||
class ChildrenSidebar extends StatelessWidget {
|
|
||||||
final List<ChildModel> children;
|
|
||||||
final String? selectedChildId;
|
|
||||||
final Function(String) onChildSelected;
|
|
||||||
final VoidCallback onAddChild;
|
|
||||||
final bool isCompact;
|
|
||||||
final bool isMobile;
|
|
||||||
|
|
||||||
const ChildrenSidebar({
|
|
||||||
Key? key,
|
|
||||||
required this.children,
|
|
||||||
this.selectedChildId,
|
|
||||||
required this.onChildSelected,
|
|
||||||
required this.onAddChild,
|
|
||||||
this.isCompact = false,
|
|
||||||
this.isMobile = false,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.all(isMobile ? 16 : 24),
|
|
||||||
color: Colors.white,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildHeader(context),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildAddChildButton(context),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Expanded(child: _buildChildrenList()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeader(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
// UserAvatar(
|
|
||||||
// size: isCompact ? 40 : 60,
|
|
||||||
// name: 'Emma Dupont', // TODO: Récupérer depuis le contexte utilisateur
|
|
||||||
// ),
|
|
||||||
if (!isCompact) ...[
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
Text(
|
|
||||||
'Emma Dupont',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(Icons.keyboard_arrow_down),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAddChildButton(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: onAddChild,
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: Text(isCompact ? 'Ajouter' : 'Ajouter un enfant'),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: isCompact ? 8 : 12,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildChildrenList() {
|
|
||||||
if (children.isEmpty) {
|
|
||||||
return const Center(
|
|
||||||
child: Text(
|
|
||||||
'Aucun enfant ajouté',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.separated(
|
|
||||||
itemCount: children.length,
|
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final child = children[index];
|
|
||||||
final isSelected = child.id == selectedChildId;
|
|
||||||
|
|
||||||
return _buildChildCard(context, child, isSelected);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildChildCard(BuildContext context, ChildModel child, bool isSelected) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onChildSelected(child.id),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? const Color(0xFF9CC5C0).withOpacity(0.1) : Colors.transparent,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? const Color(0xFF9CC5C0) : Colors.grey.shade300,
|
|
||||||
width: isSelected ? 2 : 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
// UserAvatar(
|
|
||||||
// // size: isCompact ? 32 : 40,
|
|
||||||
// // name: child.fullName,
|
|
||||||
// // imageUrl: child.photoUrl,
|
|
||||||
// ),
|
|
||||||
if (!isCompact) ...[
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
child.firstName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
_buildChildStatus(child.status),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildChildStatus(ChildStatus status) {
|
|
||||||
String label;
|
|
||||||
Color color;
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case ChildStatus.withAssistant:
|
|
||||||
label = 'En garde';
|
|
||||||
color = Colors.green;
|
|
||||||
break;
|
|
||||||
case ChildStatus.available:
|
|
||||||
label = 'Disponible';
|
|
||||||
color = Colors.blue;
|
|
||||||
break;
|
|
||||||
case ChildStatus.onHoliday:
|
|
||||||
label = 'En vacances';
|
|
||||||
color = Colors.orange;
|
|
||||||
break;
|
|
||||||
case ChildStatus.sick:
|
|
||||||
label = 'Malade';
|
|
||||||
color = Colors.red;
|
|
||||||
break;
|
|
||||||
case ChildStatus.searching:
|
|
||||||
label = 'Recherche AM';
|
|
||||||
color = Colors.purple;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: color,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,156 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
||||||
final int selectedIndex;
|
|
||||||
final ValueChanged<int> onTabChange;
|
|
||||||
|
|
||||||
const DashboardAppBar({Key? key, required this.selectedIndex, required this.onTabChange}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final isMobile = MediaQuery.of(context).size.width < 768;
|
|
||||||
return AppBar(
|
|
||||||
// backgroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
// Logo de la ville
|
|
||||||
// Container(
|
|
||||||
// height: 32,
|
|
||||||
// width: 32,
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// color: Colors.white,
|
|
||||||
// borderRadius: BorderRadius.circular(8),
|
|
||||||
// ),
|
|
||||||
// child: const Icon(
|
|
||||||
// Icons.location_city,
|
|
||||||
// color: Color(0xFF9CC5C0),
|
|
||||||
// size: 20,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.19),
|
|
||||||
const Text(
|
|
||||||
"P'tit Pas",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFF9CC5C0),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
|
||||||
|
|
||||||
// Navigation principale
|
|
||||||
_buildNavItem(context, 'Mon tableau de bord', 0),
|
|
||||||
const SizedBox(width: 24),
|
|
||||||
_buildNavItem(context, 'Trouver une nounou', 1),
|
|
||||||
const SizedBox(width: 24),
|
|
||||||
_buildNavItem(context, 'Paramètres', 2),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: isMobile
|
|
||||||
? [_buildMobileMenu(context)]
|
|
||||||
: [
|
|
||||||
// Nom de l'utilisateur
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'Jean Dupont',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Bouton déconnexion
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 16),
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => _handleLogout(context),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF9CC5C0),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text('Se déconnecter'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildNavItem(BuildContext context, String title, int index) {
|
|
||||||
final bool isActive = index == selectedIndex;
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onTabChange(index),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: isActive ? null : Border.all(color: Colors.black26),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: isActive ? Colors.white : Colors.black,
|
|
||||||
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Widget _buildMobileMenu(BuildContext context) {
|
|
||||||
return PopupMenuButton<int>(
|
|
||||||
icon: const Icon(Icons.menu, color: Colors.white),
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value == 3) {
|
|
||||||
_handleLogout(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
const PopupMenuItem(value: 0, child: Text("Mon tableau de bord")),
|
|
||||||
const PopupMenuItem(value: 1, child: Text("Trouver une nounou")),
|
|
||||||
const PopupMenuItem(value: 2, child: Text("Paramètres")),
|
|
||||||
const PopupMenuDivider(),
|
|
||||||
const PopupMenuItem(value: 3, child: Text("Se déconnecter")),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleLogout(BuildContext context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Déconnexion'),
|
|
||||||
content: const Text('Êtes-vous sûr de vouloir vous déconnecter ?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('Annuler'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
// TODO: Implémenter la logique de déconnexion
|
|
||||||
},
|
|
||||||
child: const Text('Déconnecter'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/ChildrenSidebarwidget.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/children_sidebar.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/wid_mainContentArea.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/messaging_sidebar.dart';
|
|
||||||
|
|
||||||
Widget Dashbord_body() {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
// 1️⃣ Colonne de gauche : enfants
|
|
||||||
SizedBox(
|
|
||||||
width: 250,
|
|
||||||
child: Childrensidebarwidget(
|
|
||||||
onChildSelected: (childId) {
|
|
||||||
// Met à jour l'enfant sélectionné
|
|
||||||
// Tu peux stocker cet ID dans un state `selectedChildId`
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: WMainContentArea(
|
|
||||||
// Passe l’enfant sélectionné si besoin
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/messaging_sidebar.dart';
|
|
||||||
|
|
||||||
class WMainContentArea extends StatelessWidget {
|
|
||||||
const WMainContentArea({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
color: Colors.white,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
// 🔷 Informations assistante maternelle (ligne complète)
|
|
||||||
Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const CircleAvatar(
|
|
||||||
radius: 30,
|
|
||||||
backgroundImage: AssetImage("assets/images/am_photo.jpg"), // à adapter
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
Text("Julie Dupont", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text("Taux horaire : 10€/h"),
|
|
||||||
Text("Frais journaliers : 5€"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Ouvrir le contrat
|
|
||||||
},
|
|
||||||
child: const Text("Voir le contrat"),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// 🔷 Deux colonnes : planning + messagerie
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
// 📆 Planning de garde
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: const [
|
|
||||||
Text("Planning de garde", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
||||||
SizedBox(height: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Text("Composant calendrier à intégrer ici"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
|
|
||||||
// 💬 Messagerie
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: MessagingSidebar(
|
|
||||||
conversations: [],
|
|
||||||
notifications: [],
|
|
||||||
isCompact: false,
|
|
||||||
isMobile: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,326 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/assistant_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/child_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/contract_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/event_model.dart';
|
|
||||||
|
|
||||||
class MainContentArea extends StatelessWidget {
|
|
||||||
final ChildModel? selectedChild;
|
|
||||||
final AssistantModel? selectedAssistant;
|
|
||||||
final List<EventModel> events;
|
|
||||||
final List<ContractModel> contracts;
|
|
||||||
final bool showOnlyCalendar;
|
|
||||||
final bool showOnlyContracts;
|
|
||||||
|
|
||||||
const MainContentArea({
|
|
||||||
Key? key,
|
|
||||||
this.selectedChild,
|
|
||||||
this.selectedAssistant,
|
|
||||||
required this.events,
|
|
||||||
required this.contracts,
|
|
||||||
this.showOnlyCalendar = false,
|
|
||||||
this.showOnlyContracts = false,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (!showOnlyCalendar && !showOnlyContracts) ...[
|
|
||||||
if (selectedAssistant != null) _buildAssistantProfile(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
|
|
||||||
if (showOnlyContracts || (!showOnlyCalendar && !showOnlyContracts)) ...[
|
|
||||||
_buildContractsSection(),
|
|
||||||
if (!showOnlyContracts) const SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
|
|
||||||
if (showOnlyCalendar || (!showOnlyCalendar && !showOnlyContracts)) ...[
|
|
||||||
Expanded(child: _buildCalendarSection()),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAssistantProfile() {
|
|
||||||
if (selectedAssistant == null) {
|
|
||||||
return _buildSearchAssistantCard();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.1),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 6,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF9CC5C0),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.person,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 40,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
selectedAssistant!.fullName,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Taux horaire : ${selectedAssistant!.hourlyRateFormatted}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
Text(
|
|
||||||
'Frais journaliers : ${selectedAssistant!.dailyFeesFormatted}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Navigation vers le contrat
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF9CC5C0),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text('Voir le contrat'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSearchAssistantCard() {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.search,
|
|
||||||
size: 48,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const Text(
|
|
||||||
'Aucune assistante maternelle assignée',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Trouvez une assistante maternelle pour votre enfant',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey.shade600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Navigation vers la recherche
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF9CC5C0),
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
child: const Text('Rechercher une assistante maternelle'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCalendarSection() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.1),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 6,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Planning de garde pour ${selectedChild?.firstName ?? "votre enfant"}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Mode sélection de plage
|
|
||||||
},
|
|
||||||
child: const Text('Mode sélection de plage'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Expanded(
|
|
||||||
child: _buildCalendar(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCalendar() {
|
|
||||||
// Placeholder pour le calendrier - sera développé dans FRONT-11
|
|
||||||
return const Center(
|
|
||||||
child: Text(
|
|
||||||
'Calendrier à implémenter\n(FRONT-11)',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildContractsSection() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.1),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 6,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Contrats',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
if (contracts.isEmpty)
|
|
||||||
const Text(
|
|
||||||
'Aucun contrat en cours',
|
|
||||||
style: TextStyle(color: Colors.grey),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
...contracts.map((contract) => _buildContractItem(contract)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildContractItem(ContractModel contract) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _getContractStatusColor(contract.status),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(contract.statusLabel),
|
|
||||||
),
|
|
||||||
if (contract.needsSignature)
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Action signature
|
|
||||||
},
|
|
||||||
child: const Text('Signer'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getContractStatusColor(ContractStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case ContractStatus.draft:
|
|
||||||
return Colors.grey;
|
|
||||||
case ContractStatus.pending:
|
|
||||||
return Colors.orange;
|
|
||||||
case ContractStatus.active:
|
|
||||||
return Colors.green;
|
|
||||||
case ContractStatus.ended:
|
|
||||||
return Colors.blue;
|
|
||||||
case ContractStatus.cancelled:
|
|
||||||
return Colors.red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,207 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/conversation_model.dart';
|
|
||||||
import 'package:p_tits_pas/models/m_dashbord/notification_model.dart';
|
|
||||||
|
|
||||||
class MessagingSidebar extends StatelessWidget {
|
|
||||||
final List<ConversationModel> conversations;
|
|
||||||
final List<NotificationModel> notifications;
|
|
||||||
final bool isCompact;
|
|
||||||
final bool isMobile;
|
|
||||||
|
|
||||||
const MessagingSidebar({
|
|
||||||
Key? key,
|
|
||||||
required this.conversations,
|
|
||||||
required this.notifications,
|
|
||||||
this.isCompact = false,
|
|
||||||
this.isMobile = false,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.all(isMobile ? 16 : 20),
|
|
||||||
color: Colors.white,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildMessagingHeader(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Expanded(
|
|
||||||
child: _buildMessagingContent(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildContactRPEButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMessagingHeader() {
|
|
||||||
return const Text(
|
|
||||||
'Messagerie avec Emma Dupont',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMessagingContent() {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
// Messages existants
|
|
||||||
Expanded(
|
|
||||||
child: _buildMessagesList(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
// Zone de saisie
|
|
||||||
_buildMessageInput(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMessagesList() {
|
|
||||||
if (conversations.isEmpty) {
|
|
||||||
return const Center(
|
|
||||||
child: Text(
|
|
||||||
'Aucun message',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pour la démo, on affiche quelques messages fictifs
|
|
||||||
return ListView(
|
|
||||||
children: [
|
|
||||||
_buildMessageBubble(
|
|
||||||
'Bonjour, Emma a bien dormi aujourd\'hui.',
|
|
||||||
isFromCurrentUser: false,
|
|
||||||
timestamp: DateTime.now().subtract(const Duration(hours: 2)),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_buildMessageBubble(
|
|
||||||
'Merci pour l\'information. Elle a bien mangé ?',
|
|
||||||
isFromCurrentUser: true,
|
|
||||||
timestamp: DateTime.now().subtract(const Duration(hours: 1)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMessageBubble(String content, {required bool isFromCurrentUser, required DateTime timestamp}) {
|
|
||||||
return Align(
|
|
||||||
alignment: isFromCurrentUser ? Alignment.centerRight : Alignment.centerLeft,
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 250),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isFromCurrentUser
|
|
||||||
? const Color(0xFF9CC5C0)
|
|
||||||
: Colors.grey.shade200,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
content,
|
|
||||||
style: TextStyle(
|
|
||||||
color: isFromCurrentUser ? Colors.white : Colors.black87,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
_formatTimestamp(timestamp),
|
|
||||||
style: TextStyle(
|
|
||||||
color: isFromCurrentUser
|
|
||||||
? Colors.white.withOpacity(0.8)
|
|
||||||
: Colors.grey.shade600,
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMessageInput() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.shade100,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Écrivez votre message...',
|
|
||||||
border: InputBorder.none,
|
|
||||||
isDense: true,
|
|
||||||
),
|
|
||||||
maxLines: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
CircleAvatar(
|
|
||||||
radius: 16,
|
|
||||||
backgroundColor: const Color(0xFF9CC5C0),
|
|
||||||
child: IconButton(
|
|
||||||
iconSize: 16,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Envoyer le message
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.send,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildContactRPEButton() {
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Contacter le RPE
|
|
||||||
},
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'Contacter le Relais Petite Enfance',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatTimestamp(DateTime timestamp) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final difference = now.difference(timestamp);
|
|
||||||
|
|
||||||
if (difference.inMinutes < 1) {
|
|
||||||
return 'À l\'instant';
|
|
||||||
} else if (difference.inHours < 1) {
|
|
||||||
return '${difference.inMinutes}m';
|
|
||||||
} else if (difference.inDays < 1) {
|
|
||||||
return '${difference.inHours}h';
|
|
||||||
} else {
|
|
||||||
return '${timestamp.day}/${timestamp.month}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:p_tits_pas/main.dart';
|
|
||||||
|
|
||||||
|
import 'package:petitspas/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user