marco.pms.mobileapp/lib/view/infraProject/infra_project_screen.dart
Vaibhav Surve 3dfa6e5877 feat: Add infrastructure project details and list models
- Implemented ProjectDetailsResponse and ProjectData models for handling project details.
- Created ProjectsResponse and ProjectsPageData models for listing infrastructure projects.
- Added InfraProjectScreen and InfraProjectDetailsScreen for displaying project information.
- Integrated search functionality in InfraProjectScreen to filter projects.
- Updated DailyTaskPlanningScreen and DailyProgressReportScreen to accept projectId as a parameter.
- Removed unnecessary dependencies and cleaned up code for better maintainability.
2025-12-03 16:49:46 +05:30

279 lines
9.3 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/infra_project/infra_project_screen_controller.dart';
import 'package:on_field_work/model/infra_project/infra_project_list.dart';
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
import 'package:on_field_work/view/infraProject/infra_project_details_screen.dart';
class InfraProjectScreen extends StatefulWidget {
const InfraProjectScreen({super.key});
@override
State<InfraProjectScreen> createState() => _InfraProjectScreenState();
}
class _InfraProjectScreenState extends State<InfraProjectScreen> with UIMixin {
final TextEditingController searchController = TextEditingController();
final InfraProjectController controller = Get.put(InfraProjectController());
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
controller.fetchProjects();
});
searchController.addListener(() {
controller.updateSearch(searchController.text);
});
}
Future<void> _refreshProjects() async {
await controller.fetchProjects();
}
// ---------------------------------------------------------------------------
// PROJECT CARD
// ---------------------------------------------------------------------------
Widget _buildProjectCard(ProjectData project) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
shadowColor: Colors.indigo.withOpacity(0.10),
color: Colors.white,
child: InkWell(
borderRadius: BorderRadius.circular(6),
onTap: () {
Get.to(() => InfraProjectDetailsScreen(projectId: project.id!, projectName: project.name));
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// TOP: Name + Status
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: MyText.titleMedium(
project.name ?? "-",
fontWeight: 700,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
MySpacing.width(8),
],
),
MySpacing.height(10),
if (project.shortName != null)
_buildDetailRow(
Icons.badge_outlined,
Colors.teal,
"Short Name: ${project.shortName}",
),
MySpacing.height(8),
if (project.projectAddress != null)
_buildDetailRow(
Icons.location_on_outlined,
Colors.orange,
"Address: ${project.projectAddress}",
),
MySpacing.height(8),
if (project.contactPerson != null)
_buildDetailRow(
Icons.phone,
Colors.green,
"Contact: ${project.contactPerson}",
),
MySpacing.height(12),
if (project.teamSize != null)
_buildDetailRow(
Icons.group,
Colors.indigo,
"Team Size: ${project.teamSize}",
),
],
),
),
),
);
}
Widget _buildDetailRow(IconData icon, Color color, String value) {
return Row(
children: [
Icon(icon, size: 18, color: color),
MySpacing.width(8),
Expanded(
child: MyText.bodySmall(
value,
color: Colors.grey[900],
fontWeight: 500,
fontSize: 13,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
);
}
// ---------------------------------------------------------------------------
// EMPTY STATE
// ---------------------------------------------------------------------------
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,
),
],
),
);
}
// ---------------------------------------------------------------------------
// MAIN BUILD
// ---------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
final Color appBarColor = contentTheme.primary;
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: CustomAppBar(
title: "Infra Projects",
projectName: 'All Infra Projects',
backgroundColor: appBarColor,
onBackPressed: () => Get.toNamed('/dashboard'),
),
body: Stack(
children: [
// GRADIENT BACKDROP
Container(
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
appBarColor,
appBarColor.withOpacity(0),
],
),
),
),
SafeArea(
bottom: true,
child: Column(
children: [
// SEARCH BAR
Padding(
padding: MySpacing.xy(8, 8),
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),
),
),
),
),
),
// 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: 100),
itemCount: projects.length,
separatorBuilder: (_, __) => MySpacing.height(12),
itemBuilder: (_, index) =>
_buildProjectCard(projects[index]),
),
);
}),
),
],
),
),
],
),
);
}
}