marco.pms.mobileapp/lib/view/employees/assign_employee_bottom_sheet.dart
Vaibhav Surve 943c7c7b50 feat: Add employee assignment functionality and improve employee detail view
- Implemented employee skeleton card for loading states in the UI.
- Created AssignedProjectsResponse and AssignedProject models for handling project assignments.
- Enhanced EmployeeDetailBottomSheet to include project assignment button.
- Developed AssignProjectBottomSheet for selecting projects to assign to employees.
- Introduced EmployeeDetailPage for displaying detailed employee information.
- Updated EmployeesScreen to support searching and filtering employees.
- Improved layout and UI elements for better user experience.
2025-07-22 20:10:57 +05:30

281 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/controller/employee/assign_projects_controller.dart';
import 'package:marco/model/global_project_model.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
class AssignProjectBottomSheet extends StatefulWidget {
final String employeeId;
final String jobRoleId;
const AssignProjectBottomSheet({
super.key,
required this.employeeId,
required this.jobRoleId,
});
@override
State<AssignProjectBottomSheet> createState() =>
_AssignProjectBottomSheetState();
}
class _AssignProjectBottomSheetState extends State<AssignProjectBottomSheet> {
late final AssignProjectController assignController;
@override
void initState() {
super.initState();
assignController = Get.put(
AssignProjectController(
employeeId: widget.employeeId,
jobRoleId: widget.jobRoleId,
),
tag: '${widget.employeeId}_${widget.jobRoleId}',
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SafeArea(
top: false,
child: DraggableScrollableSheet(
expand: false,
maxChildSize: 0.9,
minChildSize: 0.4,
initialChildSize: 0.7,
builder: (_, scrollController) {
return Container(
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(24)),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 12,
offset: Offset(0, -2),
),
],
),
padding: MySpacing.all(16),
child: Obx(() {
if (assignController.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
final projects = assignController.allProjects;
if (projects.isEmpty) {
return const Center(child: Text('No projects available.'));
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Drag Handle
Center(
child: Container(
width: 40,
height: 5,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(10),
),
),
),
MySpacing.height(12),
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.titleMedium('Assign to Project', fontWeight: 700),
],
),
MySpacing.height(4),
// Sub Info
MyText.bodySmall(
'Select the projects to assign this employee.',
color: Colors.grey[600],
),
MySpacing.height(8),
// Select All Toggle
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Projects (${projects.length})',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
TextButton(
onPressed: () {
assignController.toggleSelectAll();
},
child: Obx(() {
return Text(
assignController.areAllSelected()
? 'Deselect All'
: 'Select All',
style: const TextStyle(
color: Colors.blueAccent,
fontWeight: FontWeight.w600,
),
);
}),
),
],
),
// Project List
Expanded(
child: ListView.builder(
controller: scrollController,
itemCount: projects.length,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
final GlobalProjectModel project = projects[index];
return Obx(() {
final bool isSelected =
assignController.isProjectSelected(
project.id.toString(),
);
return Theme(
data: Theme.of(context).copyWith(
checkboxTheme: CheckboxThemeData(
fillColor:
WidgetStateProperty.resolveWith<Color>(
(states) {
if (states.contains(WidgetState.selected)) {
return Colors.blueAccent;
}
return Colors.white;
},
),
side: const BorderSide(
color: Colors.black,
width: 2,
),
checkColor:
WidgetStateProperty.all(Colors.white),
),
),
child: CheckboxListTile(
dense: true,
value: isSelected,
title: Text(
project.name,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
onChanged: (checked) {
assignController.toggleProjectSelection(
project.id.toString(),
checked ?? false,
);
},
activeColor: Colors.blueAccent,
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
),
),
);
});
},
),
),
MySpacing.height(16),
// Cancel & Save Buttons
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: Colors.red),
label: MyText.bodyMedium("Cancel",
color: Colors.red, fontWeight: 600),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.red),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 7,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () async {
if (assignController.selectedProjects.isEmpty) {
showAppSnackbar(
title: "Error",
message: "Please select at least one project.",
type: SnackbarType.error,
);
return;
}
await _assignProjects();
},
icon: const Icon(Icons.check_circle_outline,
color: Colors.white),
label: MyText.bodyMedium("Assign",
color: Colors.white, fontWeight: 600),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.indigo,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 7,
),
),
),
),
],
),
],
);
}),
);
},
),
);
}
Future<void> _assignProjects() async {
final success = await assignController.assignProjectsToEmployee();
if (success) {
Get.back();
showAppSnackbar(
title: "Success",
message: "Employee assigned to selected projects.",
type: SnackbarType.success,
);
} else {
showAppSnackbar(
title: "Error",
message: "Failed to assign employee.",
type: SnackbarType.error,
);
}
}
}