import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/widgets/custom_app_bar.dart'; import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart'; import 'package:on_field_work/helpers/utils/permission_constants.dart'; import 'package:on_field_work/helpers/utils/launcher_utils.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/helpers/widgets/avatar.dart'; import 'package:on_field_work/view/employees/employee_profile_screen.dart'; import 'package:on_field_work/controller/dynamicMenu/dynamic_menu_controller.dart'; import 'package:on_field_work/controller/infra_project/infra_project_screen_details_controller.dart'; import 'package:on_field_work/view/taskPlanning/daily_progress_report.dart'; import 'package:on_field_work/view/taskPlanning/daily_task_planning.dart'; import 'package:on_field_work/model/infra_project/infra_team_list_model.dart'; class InfraProjectDetailsScreen extends StatefulWidget { final String projectId; final String? projectName; const InfraProjectDetailsScreen({ super.key, required this.projectId, this.projectName, }); @override State createState() => _InfraProjectDetailsScreenState(); } class _InfraProjectDetailsScreenState extends State with SingleTickerProviderStateMixin, UIMixin { late final TabController _tabController; final DynamicMenuController menuController = Get.find(); late final InfraProjectDetailsController controller; final List<_InfraTab> _tabs = []; @override void initState() { super.initState(); controller = Get.put(InfraProjectDetailsController(projectId: widget.projectId)); _prepareTabs(); } void _prepareTabs() { _tabs.add(_InfraTab(name: "Profile", view: _buildProfileTab())); _tabs.add(_InfraTab(name: "Team", view: _buildTeamTab())); final allowedMenu = menuController.menuItems.where((m) => m.available); if (allowedMenu.any((m) => m.id == MenuItems.dailyTaskPlanning)) { _tabs.add( _InfraTab( name: "Task Planning", view: DailyTaskPlanningScreen(projectId: widget.projectId), ), ); } if (allowedMenu.any((m) => m.id == MenuItems.dailyProgressReport)) { _tabs.add( _InfraTab( name: "Task Progress", view: DailyProgressReportScreen(projectId: widget.projectId), ), ); } _tabController = TabController(length: _tabs.length, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } Widget _buildTeamTab() { return Obx(() { if (controller.teamLoading.value) { return const Center(child: CircularProgressIndicator()); } if (controller.teamErrorMessage.isNotEmpty) { return Center( child: MyText.bodyMedium(controller.teamErrorMessage.value), ); } if (controller.teamList.isEmpty) { return const Center( child: Text("No team members allocated to this project."), ); } final roleGroups = controller.groupedTeamByRole; final sortedRoleEntries = roleGroups.entries.toList() ..sort((a, b) { final aName = (a.value.isNotEmpty ? a.value.first.jobRoleName : '') .toLowerCase(); final bName = (b.value.isNotEmpty ? b.value.first.jobRoleName : '') .toLowerCase(); return aName.compareTo(bName); }); return MyRefreshIndicator( onRefresh: controller.fetchProjectTeamList, backgroundColor: Colors.indigo, color: Colors.white, child: ListView.separated( padding: const EdgeInsets.all(12), itemCount: sortedRoleEntries.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { final teamMembers = sortedRoleEntries[index].value; return _buildRoleCard(teamMembers); }, ), ); }); } Widget _buildRoleCard(List teamMembers) { teamMembers.sort((a, b) { final aName = ("${a.firstName} ${a.lastName}").trim().toLowerCase(); final bName = ("${b.firstName} ${b.lastName}").trim().toLowerCase(); return aName.compareTo(bName); }); final String roleName = (teamMembers.isNotEmpty ? (teamMembers.first.jobRoleName) : '').trim(); return Card( elevation: 3, shadowColor: Colors.black26, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // TOP: Job Role name if (roleName.isNotEmpty) ...[ MyText.bodyLarge( roleName, fontWeight: 700, ), const Divider(height: 20), ] else const Divider(height: 20), // Team members list ...teamMembers.map((allocation) { return InkWell( onTap: () { Get.to( () => EmployeeProfilePage( employeeId: allocation.employeeId, ), ); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Avatar( firstName: allocation.firstName, lastName: allocation.lastName, size: 32, ), MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleMedium( "${allocation.firstName} ${allocation.lastName}", fontWeight: 600, ), MyText.bodySmall( allocation.serviceName.isNotEmpty ? "Service: ${allocation.serviceName}" : "No Service Assigned", color: Colors.grey[700], ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ MyText.bodySmall( "Allocated", color: Colors.grey.shade500, ), MyText.bodySmall( DateFormat('d MMM yyyy').format( DateTime.parse(allocation.allocationDate), ), fontWeight: 600, ), ], ), ], ), ), ); }).toList(), ], ), ), ); } Widget _buildProfileTab() { return Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } if (controller.errorMessage.isNotEmpty) { return Center(child: Text(controller.errorMessage.value)); } final data = controller.projectDetails.value; if (data == null) { return const Center(child: Text("No project data available")); } return MyRefreshIndicator( onRefresh: controller.fetchProjectDetails, backgroundColor: Colors.indigo, color: Colors.white, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildHeaderCard(data), MySpacing.height(16), _buildProjectInfoSection(data), if (data.promoter != null) MySpacing.height(12), if (data.promoter != null) _buildPromoterInfo(data.promoter!), if (data.pmc != null) MySpacing.height(12), if (data.pmc != null) _buildPMCInfo(data.pmc!), MySpacing.height(40), ], ), ), ); }); } Widget _buildHeaderCard(dynamic data) { return Card( elevation: 2, shadowColor: Colors.black12, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon(Icons.work_outline, size: 35), MySpacing.width(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleMedium(data.name ?? "-", fontWeight: 700), MySpacing.height(6), MyText.bodySmall(data.shortName ?? "-", fontWeight: 500), ], ), ), ], ), ), ); } Widget _buildProjectInfoSection(dynamic data) { return _buildSectionCard( title: 'Project Information', titleIcon: Icons.info_outline, children: [ _buildDetailRow( icon: Icons.location_on_outlined, label: 'Address', value: data.projectAddress ?? "-", ), _buildDetailRow( icon: Icons.calendar_today_outlined, label: 'Start Date', value: data.startDate != null ? DateFormat('d/M/yyyy').format(data.startDate!) : "-", ), _buildDetailRow( icon: Icons.calendar_today_outlined, label: 'End Date', value: data.endDate != null ? DateFormat('d/M/yyyy').format(data.endDate!) : "-", ), _buildDetailRow( icon: Icons.flag_outlined, label: 'Status', value: data.projectStatus?.status ?? "-", ), _buildDetailRow( icon: Icons.person_outline, label: 'Contact Person', value: data.contactPerson ?? "-", isActionable: true, onTap: () { if (data.contactPerson != null) { LauncherUtils.launchPhone(data.contactPerson!); } }, ), ], ); } Widget _buildPromoterInfo(dynamic promoter) { return _buildSectionCard( title: 'Promoter Information', titleIcon: Icons.business_outlined, children: [ _buildDetailRow( icon: Icons.person_outline, label: 'Name', value: promoter.name ?? "-", ), _buildDetailRow( icon: Icons.phone_outlined, label: 'Contact', value: promoter.contactNumber ?? "-", isActionable: true, onTap: () => LauncherUtils.launchPhone(promoter.contactNumber ?? ""), ), _buildDetailRow( icon: Icons.email_outlined, label: 'Email', value: promoter.email ?? "-", isActionable: true, onTap: () => LauncherUtils.launchEmail(promoter.email ?? ""), ), ], ); } Widget _buildPMCInfo(dynamic pmc) { return _buildSectionCard( title: 'PMC Information', titleIcon: Icons.engineering_outlined, children: [ _buildDetailRow( icon: Icons.person_outline, label: 'Name', value: pmc.name ?? "-", ), _buildDetailRow( icon: Icons.phone_outlined, label: 'Contact', value: pmc.contactNumber ?? "-", isActionable: true, onTap: () => LauncherUtils.launchPhone(pmc.contactNumber ?? ""), ), _buildDetailRow( icon: Icons.email_outlined, label: 'Email', value: pmc.email ?? "-", isActionable: true, onTap: () => LauncherUtils.launchEmail(pmc.email ?? ""), ), ], ); } Widget _buildDetailRow({ required IconData icon, required String label, required String value, VoidCallback? onTap, bool isActionable = false, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: InkWell( onTap: isActionable ? onTap : null, borderRadius: BorderRadius.circular(5), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), child: Icon(icon, size: 20), ), MySpacing.width(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodySmall( label, fontSize: 12, color: Colors.grey[600], fontWeight: 500, ), MySpacing.height(4), MyText.bodyMedium( value, fontSize: 15, fontWeight: 500, color: isActionable ? Colors.blueAccent : Colors.black87, decoration: isActionable ? TextDecoration.underline : TextDecoration.none, ), ], ), ), ], ), ), ); } Widget _buildSectionCard({ required String title, required IconData titleIcon, required List children, }) { return Card( elevation: 2, shadowColor: Colors.black12, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(titleIcon, size: 20), MySpacing.width(8), MyText.bodyLarge( title, fontSize: 16, fontWeight: 700, color: Colors.black87, ), ], ), MySpacing.height(8), const Divider(), ...children, ], ), ), ); } @override Widget build(BuildContext context) { final Color appBarColor = contentTheme.primary; return Scaffold( backgroundColor: const Color(0xFFF1F1F1), appBar: CustomAppBar( title: "Infra Projects", onBackPressed: () => Get.back(), projectName: widget.projectName, backgroundColor: appBarColor, ), body: Stack( children: [ Container( height: 50, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [appBarColor, appBarColor.withOpacity(0)], ), ), ), SafeArea( top: false, bottom: true, child: Column( children: [ PillTabBar( controller: _tabController, tabs: _tabs.map((e) => e.name).toList(), selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary, ), Expanded( child: TabBarView( controller: _tabController, children: _tabs.map((e) => e.view).toList(), ), ), ], ), ), ], ), ); } } /// INTERNAL MODEL class _InfraTab { final String name; final Widget view; _InfraTab({required this.name, required this.view}); }