import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/extensions/board_data_extension.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/board_response/board_response.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import '../../../core/constants/assets.dart'; import '../../../core/enums/task_status.dart'; import '../../../core/utils/app_toast.dart'; import '../../../view_models/board_view_model.dart'; import '../strings/board_strings.dart'; class BoardScreen extends StatefulWidget { const BoardScreen({super.key}); @override State createState() => _BoardScreenState(); } class _BoardScreenState extends State { late BoardViewModel _boardViewModel; @override void initState() { super.initState(); _boardViewModel = context.read(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { _boardViewModel.getBoard(); }); } void _updateTaskStatus(BoardData task, TaskStatus newStatus) { if (task.statusEnum == newStatus) { return; } final oldStatus = task.statusEnum; // update task status in ViewModel _boardViewModel.updateTaskStatus(task.id!, newStatus.value); _showStatusChangeMessage(context, task, oldStatus, newStatus); } void _showStatusChangeMessage( BuildContext context, BoardData task, TaskStatus oldStatus, TaskStatus newStatus, ) { final statusNames = { TaskStatus.toDo: BoardStrings.toDo, TaskStatus.inProgress: BoardStrings.inProgress, TaskStatus.done: BoardStrings.done, }; AppToast.success( context, BoardStrings.taskMovedMessage( statusNames[oldStatus]!, statusNames[newStatus]!, ), ); } @override Widget build(BuildContext context) { SizeConfig.init(context); return Scaffold( backgroundColor: AppColors.backgroundColor, body: Column( children: [ const DesktopNavigationWidget( currentIndex: 2, // Tenders index ), Consumer( builder: (context, viewModel, child) { if (viewModel.isLoading) { return const Expanded( child: Center( child: CircularProgressIndicator( color: AppColors.secondary50, ), ), ); } if (viewModel.errorMessage != null) { return Expanded( child: Center(child: Text(viewModel.errorMessage!)), ); } if (viewModel.hasData == false) { return const Expanded( child: Center(child: Text(BoardStrings.emptyListText)), ); } return Expanded( child: SingleChildScrollView( child: SizedBox( width: 740, height: 735, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 32), Text( BoardStrings.boardTitle, style: TextStyle( fontSize: 20.0.sp(), fontWeight: FontWeight.w600, color: AppColors.titleColor, ), ), const SizedBox(height: 20), Expanded( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: AppColors.primary20, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // To Do Expanded( child: _BoardColumn( title: BoardStrings.toDo, color: AppColors.primary20, textColor: const Color(0xFF1976D2), tasks: viewModel.getTasksByStatus( TaskStatus.toDo, ), status: TaskStatus.toDo, onTaskDropped: _updateTaskStatus, isValidMove: viewModel.isValidMove, ), ), const SizedBox(width: 12), // In Progress Expanded( child: _BoardColumn( title: BoardStrings.inProgress, color: AppColors.orange20, textColor: AppColors.orange, tasks: viewModel.getTasksByStatus( TaskStatus.inProgress, ), status: TaskStatus.inProgress, onTaskDropped: _updateTaskStatus, isValidMove: viewModel.isValidMove, ), ), const SizedBox(width: 12), // Done Expanded( child: _BoardColumn( title: BoardStrings.done, color: AppColors.secondary20, textColor: AppColors.secondary70, tasks: viewModel.getTasksByStatus( TaskStatus.done, ), status: TaskStatus.done, onTaskDropped: _updateTaskStatus, isValidMove: viewModel.isValidMove, ), ), ], ), ), ), ], ), ), ), ); }, ), ], ), ); } } class _BoardColumn extends StatelessWidget { final String title; final Color color; final Color textColor; final List tasks; final TaskStatus status; final Function(BoardData, TaskStatus) onTaskDropped; final bool Function(TaskStatus, TaskStatus) isValidMove; const _BoardColumn({ required this.title, required this.color, required this.textColor, required this.tasks, required this.status, required this.onTaskDropped, required this.isValidMove, }); @override Widget build(BuildContext context) { return DragTarget( onWillAcceptWithDetails: (details) { return isValidMove(details.data.statusEnum, status); }, onAcceptWithDetails: (details) { onTaskDropped(details.data, status); }, builder: (context, candidateData, rejectedData) { final isHovering = candidateData.isNotEmpty; return Container( decoration: BoxDecoration( color: isHovering ? color.withValues(alpha: 0.3) : AppColors.primary0, borderRadius: BorderRadius.circular(12), border: Border.all( color: isHovering ? textColor : AppColors.borderBlue, width: isHovering ? 2 : 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, height: 34, margin: const EdgeInsets.all(8), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(4), ), alignment: Alignment.center, child: Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: textColor, ), ), ), Expanded( child: ListView.builder( padding: const EdgeInsets.all(12), itemCount: tasks.length, itemBuilder: (context, index) { return _TaskCard(task: tasks[index]); }, ), ), ], ), ); }, ); } } class _TaskCard extends StatelessWidget { final BoardData task; const _TaskCard({required this.task}); String _formatDate(DateTime date) { return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } @override Widget build(BuildContext context) { return Draggable( data: task, feedback: Material( elevation: 8, borderRadius: BorderRadius.circular(12), child: Container( width: 280, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.primary0, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.borderBlue, width: 2), ), child: _buildCardContent(context), ), ), childWhenDragging: Opacity(opacity: 0.3, child: _buildCard(context)), child: _buildCard(context), ); } Widget _buildCard(BuildContext context) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.primary0, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.grey30), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: _buildCardContent(context), ); } Widget _buildCardContent(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 24.0, padding: EdgeInsets.symmetric(horizontal: 10.0.w()), decoration: BoxDecoration( color: task.statusEnum == TaskStatus.toDo ? AppColors.primary20 : task.statusEnum == TaskStatus.inProgress ? AppColors.orange10 : AppColors.secondary10, borderRadius: BorderRadius.circular(4), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${BoardStrings.deadlineLabel} :', style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.w500, color: AppColors.textBlue, ), ), Text( _formatDate(task.deadline ?? DateTime.now()), style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.w500, color: AppColors.grey80, ), ), ], ), ), const SizedBox(height: 12), Text( task.description!, style: TextStyle(fontSize: 14, color: AppColors.grey70), maxLines: 3, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), decoration: BoxDecoration( color: task.isReady ?? false ? AppColors.primary20 : AppColors.red0, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: task.isReady! ? AppColors.textBlue : AppColors.error, shape: BoxShape.circle, ), ), const SizedBox(width: 6), Text( task.isReady! ? BoardStrings.readyLabel : BoardStrings.incompleteLabel, style: TextStyle( fontSize: 12, color: task.isReady! ? AppColors.textBlue : AppColors.red20, fontWeight: FontWeight.w400, ), ), ], ), ), const SizedBox(height: 12), Row( children: [ SvgPicture.asset(AssetsManager.profile), const SizedBox(width: 4), Text( task.assignedTo!, style: TextStyle(fontSize: 12, color: AppColors.grey60), ), ], ), ], ); } }