Files
Kifi/kifi-app/lib/features/projects/presentation/project_board_screen.dart

229 lines
7.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../domain/project.dart';
import '../providers/project_provider.dart';
import 'widgets/add_task_sheet.dart';
import 'widgets/task_card.dart';
import 'widgets/add_project_sheet.dart';
import '../../sales/presentation/invoice_builder_screen.dart';
import '../../sales/domain/invoice.dart';
class ProjectBoardScreen extends ConsumerStatefulWidget {
final Project project;
const ProjectBoardScreen({super.key, required this.project});
@override
ConsumerState<ProjectBoardScreen> createState() => _ProjectBoardScreenState();
}
class _ProjectBoardScreenState extends ConsumerState<ProjectBoardScreen> {
@override
Widget build(BuildContext context) {
final tasksAsync = ref.watch(projectTasksProvider(widget.project.id));
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text(
widget.project.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
actions: [
IconButton(
icon: const Icon(LucideIcons.info),
tooltip: 'Project Details',
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => AddProjectSheet(project: widget.project),
);
},
),
],
),
body: tasksAsync.when(
data: (tasks) {
final columns = [
_ColumnDef('To Do', 'TODO', Colors.blueGrey, LucideIcons.circle),
_ColumnDef(
'In Progress',
'IN_PROGRESS',
Colors.blue,
LucideIcons.loader,
),
_ColumnDef('Review', 'REVIEW', Colors.orange, LucideIcons.eye),
_ColumnDef('Done', 'DONE', Colors.green, LucideIcons.checkCircle),
];
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: columns.map((col) {
final columnTasks = tasks
.where((t) => t.status == col.status)
.toList();
return _buildColumn(
col,
columnTasks,
showAdd: col.status == 'TODO',
);
}).toList(),
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
),
);
}
Widget _buildColumn(_ColumnDef col, List tasks, {bool showAdd = false}) {
return Container(
width: 290,
margin: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: col.color.withValues(alpha: 0.04),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: col.color.withValues(alpha: 0.12)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Column header
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: col.color.withValues(alpha: 0.08),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(16),
),
),
child: Row(
children: [
Icon(col.icon, size: 16, color: col.color),
const SizedBox(width: 8),
Text(
col.label,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: col.color,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: col.color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${tasks.length}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: col.color,
),
),
),
],
),
),
// Task cards
...tasks.map(
(t) => TaskCard(
task: t,
onEdit: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) =>
AddTaskSheet(projectId: widget.project.id, task: t),
);
},
onGenerateInvoice: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => InvoiceBuilderScreen(
initialCustomerId: widget.project.customerId,
initialItems: [
InvoiceItem(
id: 0,
invoiceId: 0,
productId: null,
description: t.title,
quantity: t.hoursLogged,
unitPrice: t.hourlyRate,
total: t.hoursLogged * t.hourlyRate,
),
],
),
),
);
},
),
),
if (showAdd)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: col.color,
side: BorderSide(color: col.color.withValues(alpha: 0.3)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(vertical: 10),
),
icon: const Icon(LucideIcons.plus, size: 16),
label: const Text(
'Add Task',
style: TextStyle(fontWeight: FontWeight.w600),
),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) =>
AddTaskSheet(projectId: widget.project.id),
);
},
),
),
),
const SizedBox(height: 8),
],
),
);
}
}
class _ColumnDef {
final String label;
final String status;
final Color color;
final IconData icon;
const _ColumnDef(this.label, this.status, this.color, this.icon);
}