a03e66f66b
- Added new color definitions for secondary and orange shades in AppColors. - Updated TaskItem model to remove title and improve description handling. - Refactored BoardScreen to utilize new color definitions and improve task display. - Enhanced task status messaging and UI consistency across task cards.
433 lines
13 KiB
Dart
433 lines
13 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/svg.dart';
|
|
import 'package:tm_app/core/theme/colors.dart';
|
|
import 'package:tm_app/core/utils/size_config.dart';
|
|
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
|
|
|
import '../../core/constants/assets.dart';
|
|
|
|
class TaskItem {
|
|
final String id;
|
|
final String description;
|
|
final DateTime deadline;
|
|
final String assignedTo;
|
|
final bool isReady;
|
|
TaskStatus status;
|
|
|
|
TaskItem({
|
|
required this.id,
|
|
required this.description,
|
|
required this.deadline,
|
|
required this.assignedTo,
|
|
required this.isReady,
|
|
required this.status,
|
|
});
|
|
}
|
|
|
|
enum TaskStatus { toDo, inProgress, done }
|
|
|
|
class BoardScreen extends StatefulWidget {
|
|
const BoardScreen({super.key});
|
|
|
|
@override
|
|
State<BoardScreen> createState() => _BoardScreenState();
|
|
}
|
|
|
|
class _BoardScreenState extends State<BoardScreen> {
|
|
List<TaskItem> tasks = [
|
|
TaskItem(
|
|
id: '1',
|
|
description:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
deadline: DateTime(2025, 6, 15),
|
|
assignedTo: 'Pratyush Tewari',
|
|
isReady: false,
|
|
status: TaskStatus.toDo,
|
|
),
|
|
TaskItem(
|
|
id: '2',
|
|
description:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
deadline: DateTime(2025, 6, 15),
|
|
assignedTo: 'Pratyush Tewari',
|
|
isReady: true,
|
|
status: TaskStatus.toDo,
|
|
),
|
|
TaskItem(
|
|
id: '3',
|
|
description:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
deadline: DateTime(2025, 6, 15),
|
|
assignedTo: 'Pratyush Tewari',
|
|
isReady: false,
|
|
status: TaskStatus.inProgress,
|
|
),
|
|
TaskItem(
|
|
id: '4',
|
|
description:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
deadline: DateTime(2025, 6, 15),
|
|
assignedTo: 'Pratyush Tewari',
|
|
isReady: true,
|
|
status: TaskStatus.inProgress,
|
|
),
|
|
TaskItem(
|
|
id: '5',
|
|
description:
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
deadline: DateTime(2025, 6, 15),
|
|
assignedTo: 'Pratyush Tewari',
|
|
isReady: false,
|
|
status: TaskStatus.done,
|
|
),
|
|
];
|
|
|
|
void _updateTaskStatus(TaskItem task, TaskStatus newStatus) {
|
|
if (task.status == newStatus) {
|
|
return;
|
|
}
|
|
|
|
final oldStatus = task.status;
|
|
setState(() {
|
|
task.status = newStatus;
|
|
});
|
|
|
|
_showStatusChangeMessage(context, task, oldStatus, newStatus);
|
|
}
|
|
|
|
void _showStatusChangeMessage(
|
|
BuildContext context,
|
|
TaskItem task,
|
|
TaskStatus oldStatus,
|
|
TaskStatus newStatus,
|
|
) {
|
|
final statusNames = {
|
|
TaskStatus.toDo: 'To Do',
|
|
TaskStatus.inProgress: 'In Progress',
|
|
TaskStatus.done: 'Done',
|
|
};
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
'Task "${task.description}" moved from ${statusNames[oldStatus]} to ${statusNames[newStatus]}',
|
|
style: const TextStyle(fontFamily: 'Peyda'),
|
|
),
|
|
duration: const Duration(seconds: 2),
|
|
backgroundColor: AppColors.successColor,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<TaskItem> _getTasksByStatus(TaskStatus status) {
|
|
return tasks.where((task) => task.status == status).toList();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
SizeConfig.init(context);
|
|
return Scaffold(
|
|
backgroundColor: AppColors.backgroundColor,
|
|
|
|
body: Column(
|
|
children: [
|
|
const DesktopNavigationWidget(
|
|
currentIndex: 2, // Tenders index
|
|
),
|
|
SizedBox(
|
|
width: 740,
|
|
height: 735,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 64),
|
|
Text(
|
|
'My board',
|
|
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: 'To do',
|
|
color: AppColors.primary20,
|
|
textColor: const Color(0xFF1976D2),
|
|
tasks: _getTasksByStatus(TaskStatus.toDo),
|
|
status: TaskStatus.toDo,
|
|
onTaskDropped: _updateTaskStatus,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
// In Progress
|
|
Expanded(
|
|
child: _BoardColumn(
|
|
title: 'In progress',
|
|
color: AppColors.orange20,
|
|
textColor: AppColors.orange,
|
|
tasks: _getTasksByStatus(TaskStatus.inProgress),
|
|
status: TaskStatus.inProgress,
|
|
onTaskDropped: _updateTaskStatus,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
// Done
|
|
Expanded(
|
|
child: _BoardColumn(
|
|
title: 'Done',
|
|
color: AppColors.secondary20,
|
|
textColor: AppColors.secondary70,
|
|
tasks: _getTasksByStatus(TaskStatus.done),
|
|
status: TaskStatus.done,
|
|
onTaskDropped: _updateTaskStatus,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _BoardColumn extends StatelessWidget {
|
|
final String title;
|
|
final Color color;
|
|
final Color textColor;
|
|
final List<TaskItem> tasks;
|
|
final TaskStatus status;
|
|
final Function(TaskItem, TaskStatus) onTaskDropped;
|
|
|
|
const _BoardColumn({
|
|
required this.title,
|
|
required this.color,
|
|
required this.textColor,
|
|
required this.tasks,
|
|
required this.status,
|
|
required this.onTaskDropped,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DragTarget<TaskItem>(
|
|
onWillAcceptWithDetails: (details) {
|
|
return details.data.status != 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 TaskItem 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<TaskItem>(
|
|
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.status == TaskStatus.toDo
|
|
? AppColors.primary20
|
|
: task.status == TaskStatus.inProgress
|
|
? AppColors.orange10
|
|
: AppColors.secondary10,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Deadline :',
|
|
style: TextStyle(
|
|
fontSize: 14.0,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.textBlue,
|
|
),
|
|
),
|
|
Text(
|
|
_formatDate(task.deadline),
|
|
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 ? 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 ? 'Ready' : 'Incomplete',
|
|
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),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|