feat: enhance notification action handling and add refresh indicators in ServiceProjectDetailsScreen
This commit is contained in:
parent
e9075dcdf5
commit
516d6b0489
@ -414,12 +414,17 @@ class NotificationActionHandler {
|
||||
required String notFoundMessage,
|
||||
required String successMessage,
|
||||
}) {
|
||||
if (!Get.isRegistered<T>()) {
|
||||
_logger.w(notFoundMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final controller = Get.find<T>();
|
||||
onFound(controller);
|
||||
_logger.i(successMessage);
|
||||
} catch (e) {
|
||||
_logger.w(notFoundMessage);
|
||||
_logger.w('⚠️ Error updating controller: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,12 +12,14 @@ import 'package:on_field_work/helpers/widgets/avatar.dart';
|
||||
import 'package:on_field_work/model/service_project/service_project_allocation_bottomsheet.dart';
|
||||
import 'package:on_field_work/model/employees/employee_model.dart';
|
||||
import 'package:on_field_work/view/service_project/jobs_tab.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||
|
||||
class ServiceProjectDetailsScreen extends StatefulWidget {
|
||||
final String projectId;
|
||||
final String? projectName;
|
||||
|
||||
const ServiceProjectDetailsScreen({super.key, required this.projectId , this.projectName});
|
||||
const ServiceProjectDetailsScreen(
|
||||
{super.key, required this.projectId, this.projectName});
|
||||
|
||||
@override
|
||||
State<ServiceProjectDetailsScreen> createState() =>
|
||||
@ -174,13 +176,20 @@ class _ServiceProjectDetailsScreenState
|
||||
|
||||
Widget _buildProfileTab() {
|
||||
final project = controller.projectDetail.value;
|
||||
|
||||
if (project == null) {
|
||||
return Center(child: MyText.bodyMedium("No project data"));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: MySpacing.all(12),
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await controller.fetchProjectDetail();
|
||||
},
|
||||
backgroundColor: Colors.indigo,
|
||||
color: Colors.white,
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: MySpacing.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@ -335,77 +344,85 @@ class _ServiceProjectDetailsScreenState
|
||||
return Center(child: MyText.bodyMedium("No team members found"));
|
||||
}
|
||||
|
||||
// Group team members by their role ID
|
||||
// Group team members by role
|
||||
final Map<String, List> roleGroups = {};
|
||||
for (var team in controller.teamList) {
|
||||
roleGroups.putIfAbsent(team.teamRole.id, () => []).add(team);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: roleGroups.keys.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final roleId = roleGroups.keys.elementAt(index);
|
||||
final teamMembers = roleGroups[roleId]!;
|
||||
final roleName = teamMembers.first.teamRole.name;
|
||||
|
||||
return Card(
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
elevation: 3,
|
||||
shadowColor: Colors.black26,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Role header
|
||||
MyText.bodyLarge(
|
||||
roleName,
|
||||
fontWeight: 700,
|
||||
color: Colors.black87,
|
||||
),
|
||||
const Divider(height: 20, thickness: 1),
|
||||
// List of team members inside this role card
|
||||
...teamMembers.map((team) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Avatar(
|
||||
firstName: team.employee.firstName,
|
||||
lastName: team.employee.lastName,
|
||||
size: 32,
|
||||
imageUrl: (team.employee.photo?.isNotEmpty ?? false)
|
||||
? team.employee.photo
|
||||
: null,
|
||||
),
|
||||
MySpacing.width(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleMedium(
|
||||
"${team.employee.firstName} ${team.employee.lastName}",
|
||||
fontWeight: 600,
|
||||
),
|
||||
MyText.bodySmall(
|
||||
"Status: ${team.isActive ? 'Active' : 'Inactive'}",
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await controller.fetchProjectTeams();
|
||||
},
|
||||
backgroundColor: Colors.indigo,
|
||||
color: Colors.white,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: roleGroups.keys.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final roleId = roleGroups.keys.elementAt(index);
|
||||
final teamMembers = roleGroups[roleId]!;
|
||||
final roleName = teamMembers.first.teamRole.name;
|
||||
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
elevation: 3,
|
||||
shadowColor: Colors.black26,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Role header
|
||||
MyText.bodyLarge(
|
||||
roleName,
|
||||
fontWeight: 700,
|
||||
color: Colors.black87,
|
||||
),
|
||||
const Divider(height: 20, thickness: 1),
|
||||
// List of team members inside this role card
|
||||
...teamMembers.map((team) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Avatar(
|
||||
firstName: team.employee.firstName,
|
||||
lastName: team.employee.lastName,
|
||||
size: 32,
|
||||
imageUrl:
|
||||
(team.employee.photo?.isNotEmpty ?? false)
|
||||
? team.employee.photo
|
||||
: null,
|
||||
),
|
||||
MySpacing.width(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleMedium(
|
||||
"${team.employee.firstName} ${team.employee.lastName}",
|
||||
fontWeight: 600,
|
||||
),
|
||||
MyText.bodySmall(
|
||||
"Status: ${team.isActive ? 'Active' : 'Inactive'}",
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -457,7 +474,10 @@ class _ServiceProjectDetailsScreenState
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildProfileTab(),
|
||||
JobsTab(scrollController: _jobScrollController, projectName: widget.projectName ?? '',),
|
||||
JobsTab(
|
||||
scrollController: _jobScrollController,
|
||||
projectName: widget.projectName ?? '',
|
||||
),
|
||||
_buildTeamsTab(),
|
||||
],
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user