marco.pms.mobileapp/lib/view/service_project/service_project_screen.dart

365 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
import 'package:marco/controller/service_project/service_project_screen_controller.dart';
import 'package:marco/model/service_project/service_projects_list_model.dart';
import 'package:marco/helpers/utils/date_time_utils.dart ';
import 'package:marco/view/service_project/service_project_details_screen.dart';
class ServiceProjectScreen extends StatefulWidget {
const ServiceProjectScreen({super.key});
@override
State<ServiceProjectScreen> createState() => _ServiceProjectScreenState();
}
class _ServiceProjectScreenState extends State<ServiceProjectScreen>
with UIMixin {
final TextEditingController searchController = TextEditingController();
final ServiceProjectController controller =
Get.put(ServiceProjectController());
@override
void initState() {
super.initState();
controller.fetchProjects();
searchController.addListener(() {
controller.updateSearch(searchController.text);
});
}
Future<void> _refreshProjects() async {
await controller.fetchProjects();
}
Widget _buildProjectCard(ProjectItem project) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
shadowColor: Colors.indigo.withOpacity(0.10),
color: Colors.white,
child: InkWell(
borderRadius: BorderRadius.circular(14),
onTap: () {
// Navigate to ServiceProjectDetailsScreen
Get.to(
() => ServiceProjectDetailsScreen(projectId: project.id),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Header Row: Avatar | Name & Tags | Status
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium(
project.name,
fontWeight: 800,
),
MySpacing.height(2),
Row(
children: [
if (project.shortName.isNotEmpty)
_buildTag(project.shortName),
if (project.shortName.isNotEmpty)
MySpacing.width(6),
Icon(Icons.location_on,
size: 15, color: Colors.deepOrange.shade400),
MySpacing.width(2),
Flexible(
child: MyText.bodySmall(
project.projectAddress,
color: Colors.grey[700],
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
],
),
MySpacing.height(12),
_buildDetailRow(
Icons.date_range_outlined,
Colors.teal,
"${DateTimeUtils.convertUtcToLocal(project.startDate.toIso8601String(), format: DateTimeUtils.defaultFormat)} To "
"${DateTimeUtils.convertUtcToLocal(project.endDate.toIso8601String(), format: DateTimeUtils.defaultFormat)}",
fontSize: 13,
),
MySpacing.height(12),
/// Stats
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildStatColumn(Icons.people_alt_rounded, "Team",
"${project.teamSize}", Colors.blue[700]),
_buildStatColumn(
Icons.check_circle,
"Completed",
"${project.completedWork.toStringAsFixed(1)}%",
Colors.green[600]),
_buildStatColumn(
Icons.pending,
"Planned",
"${project.plannedWork.toStringAsFixed(1)}%",
Colors.orange[800]),
],
),
],
),
),
),
);
}
// Helper to build colored tags
Widget _buildTag(String label) {
return Container(
decoration: BoxDecoration(
color: Colors.indigo.withOpacity(0.08),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child:
MyText.labelSmall(label, color: Colors.indigo[700], fontWeight: 500),
);
}
// Helper for detail row with icon and text
Widget _buildDetailRow(IconData icon, Color iconColor, String value,
{double fontSize = 12}) {
return Row(
children: [
Icon(icon, size: 19, color: iconColor),
MySpacing.width(8),
Flexible(
child: MyText.bodySmall(
value,
color: Colors.grey[900],
fontWeight: 500,
fontSize: fontSize,
overflow: TextOverflow.ellipsis,
),
),
],
);
}
// Helper for stats column (icon + label + value)
Widget _buildStatColumn(
IconData icon, String label, String value, Color? color) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(icon, color: color, size: 19),
SizedBox(height: 3),
MyText.labelSmall(value, color: color, fontWeight: 700),
MyText.bodySmall(label, color: Colors.grey[500], fontSize: 11),
],
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.work_outline, size: 60, color: Colors.grey),
MySpacing.height(18),
MyText.titleMedium('No matching projects found.',
fontWeight: 600, color: Colors.grey),
MySpacing.height(10),
MyText.bodySmall('Try adjusting your filters or refresh.',
color: Colors.grey),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
/// APPBAR
appBar: PreferredSize(
preferredSize: const Size.fromHeight(72),
child: AppBar(
backgroundColor: const Color(0xFFF5F5F5),
elevation: 0.5,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Padding(
padding: MySpacing.xy(16, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new,
color: Colors.black, size: 20),
onPressed: () => Get.back(),
),
MySpacing.width(8),
MyText.titleLarge(
'Service Projects',
fontWeight: 700,
color: Colors.black,
),
],
),
),
),
),
body: Column(
children: [
/// SEARCH + FILTER BAR
Padding(
padding: MySpacing.xy(8, 8),
child: Row(
children: [
Expanded(
child: SizedBox(
height: 35,
child: TextField(
controller: searchController,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(horizontal: 12),
prefixIcon: const Icon(Icons.search,
size: 20, color: Colors.grey),
suffixIcon: ValueListenableBuilder<TextEditingValue>(
valueListenable: searchController,
builder: (context, value, _) {
if (value.text.isEmpty)
return const SizedBox.shrink();
return IconButton(
icon: const Icon(Icons.clear,
size: 20, color: Colors.grey),
onPressed: () {
searchController.clear();
controller.updateSearch('');
},
);
},
),
hintText: 'Search projects...',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey.shade300),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey.shade300),
),
),
),
),
),
MySpacing.width(8),
Container(
height: 35,
width: 35,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(5),
),
child: IconButton(
icon:
const Icon(Icons.tune, size: 20, color: Colors.black87),
onPressed: () {
// TODO: Open filter bottom sheet
},
),
),
MySpacing.width(10),
Container(
height: 35,
width: 35,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(5),
),
child: PopupMenuButton<int>(
padding: EdgeInsets.zero,
icon: const Icon(Icons.more_vert,
size: 20, color: Colors.black87),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
itemBuilder: (context) => [
const PopupMenuItem<int>(
enabled: false,
height: 30,
child: Text("Actions",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey)),
),
const PopupMenuItem<int>(
value: 1,
child: Row(
children: [
SizedBox(width: 10),
Expanded(child: Text("Manage Projects")),
Icon(Icons.chevron_right,
size: 20, color: Colors.indigo),
],
),
),
],
),
),
],
),
),
/// PROJECT LIST
Expanded(
child: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
final projects = controller.filteredProjects;
return MyRefreshIndicator(
onRefresh: _refreshProjects,
backgroundColor: Colors.indigo,
color: Colors.white,
child: projects.isEmpty
? _buildEmptyState()
: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
padding: MySpacing.only(
left: 8, right: 8, top: 4, bottom: 80),
itemCount: projects.length,
separatorBuilder: (_, __) => MySpacing.height(12),
itemBuilder: (_, index) =>
_buildProjectCard(projects[index]),
),
);
}),
),
],
),
);
}
}