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

288 lines
9.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
import 'package:on_field_work/helpers/widgets/my_text.dart';
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
import 'package:on_field_work/controller/service_project/service_project_screen_controller.dart';
import 'package:on_field_work/model/service_project/service_projects_list_model.dart';
import 'package:on_field_work/helpers/utils/date_time_utils.dart';
import 'package:on_field_work/view/service_project/service_project_details_screen.dart';
import 'package:on_field_work/helpers/widgets/custom_app_bar.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();
// Fetch projects safely after first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
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,
projectName: project.name,
));
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Project Header
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium(
project.name,
fontWeight: 700,
),
MySpacing.height(4),
],
),
),
if (project.status?.status.isNotEmpty ?? false)
Container(
decoration: BoxDecoration(
color: Colors.indigo.withOpacity(0.08),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
child: MyText.labelSmall(
project.status!.status,
color: Colors.indigo[700],
fontWeight: 600,
),
),
],
),
MySpacing.height(10),
/// Assigned Date
_buildDetailRow(
Icons.date_range_outlined,
Colors.teal,
"Assigned: ${DateTimeUtils.convertUtcToLocal(project.assignedDate.toIso8601String(), format: DateTimeUtils.defaultFormat)}",
fontSize: 13,
),
MySpacing.height(8),
/// Client Info
if (project.client != null)
_buildDetailRow(
Icons.account_circle_outlined,
Colors.indigo,
"Client: ${project.client!.name} (${project.client!.contactPerson})",
fontSize: 13,
),
MySpacing.height(8),
/// Contact Info
_buildDetailRow(
Icons.phone,
Colors.green,
"Contact: ${project.contactName} (${project.contactPhone})",
fontSize: 13,
),
MySpacing.height(12),
/// Services List
if (project.services.isNotEmpty)
Wrap(
spacing: 6,
runSpacing: 4,
children: project.services
.map((service) => _buildServiceChip(service.name))
.toList(),
),
],
),
),
),
);
}
Widget _buildServiceChip(String name) {
return Container(
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
child: MyText.labelSmall(
name,
color: Colors.orange[800],
fontWeight: 500,
),
);
}
Widget _buildDetailRow(IconData icon, Color iconColor, String value,
{double fontSize = 12}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(icon, size: 18, color: iconColor),
MySpacing.width(8),
Flexible(
child: MyText.bodySmall(
value,
color: Colors.grey[900],
fontWeight: 500,
fontSize: fontSize,
),
),
],
);
}
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: CustomAppBar(
title: "Service Projects",
projectName: 'All Service Projects',
onBackPressed: () => Get.toNamed('/dashboard'),
),
body: Column(
children: [
/// Search bar and actions
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),
),
),
),
),
),
],
),
),
/// 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]),
),
);
}),
),
],
),
);
}
}