marco.pms.mobileapp/lib/view/dashboard/dashboard_screen.dart

489 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:get/get.dart';
import 'package:on_field_work/controller/dashboard/dashboard_controller.dart';
import 'package:on_field_work/controller/project_controller.dart';
import 'package:on_field_work/controller/dynamicMenu/dynamic_menu_controller.dart';
import 'package:on_field_work/helpers/services/storage/local_storage.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/dashbaord/expense_breakdown_chart.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/expense_by_status_widget.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/monthly_expense_dashboard_chart.dart';
import 'package:on_field_work/view/layouts/layout.dart';
import 'package:on_field_work/helpers/utils/permission_constants.dart';
import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart ';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
final DashboardController dashboardController =
Get.put(DashboardController(), permanent: true);
final DynamicMenuController menuController = Get.put(DynamicMenuController());
final ProjectController projectController = Get.find<ProjectController>();
bool hasMpin = true;
@override
void initState() {
super.initState();
_checkMpinStatus();
}
Future<void> _checkMpinStatus() async {
hasMpin = await LocalStorage.getIsMpin();
if (mounted) setState(() {});
}
//---------------------------------------------------------------------------
// REUSABLE CARD (smaller, minimal)
//---------------------------------------------------------------------------
Widget _cardWrapper({required Widget child}) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
border: Border.all(color: Colors.black12.withOpacity(.04)),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(.05),
blurRadius: 12,
offset: const Offset(0, 4),
)
],
),
child: child,
);
}
//---------------------------------------------------------------------------
// SECTION TITLE
//---------------------------------------------------------------------------
Widget _sectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(left: 4, bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.black87,
),
),
);
}
Widget _conditionalQuickActionCard() {
String status = "1"; // <-- change as needed
bool isCheckedIn = status == "O";
// Button color remains the same
Color buttonColor =
isCheckedIn ? Colors.red.shade700 : Colors.green.shade700;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
gradient: LinearGradient(
colors: [
contentTheme.primary.withOpacity(0.3), // lighter/faded
contentTheme.primary.withOpacity(0.6), // slightly stronger
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(0.05),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title & Status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isCheckedIn ? "Checked-In" : "Not Checked-In",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Icon(
isCheckedIn ? LucideIcons.log_out : LucideIcons.log_in,
color: Colors.white,
size: 24,
),
],
),
const SizedBox(height: 8),
// Description
Text(
isCheckedIn
? "You are currently checked-in. Don't forget to check-out after your work."
: "You are not checked-in yet. Please check-in to start your work.",
style: const TextStyle(color: Colors.white70, fontSize: 13),
),
const SizedBox(height: 12),
// Action Button (solid color)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton.icon(
onPressed: () {
// Check-In / Check-Out action
},
icon: Icon(
isCheckedIn ? LucideIcons.log_out : LucideIcons.log_in,
size: 16,
),
label: Text(isCheckedIn ? "Check-Out" : "Check-In"),
style: ElevatedButton.styleFrom(
backgroundColor: buttonColor,
foregroundColor: Colors.white,
),
),
],
),
],
),
);
}
//---------------------------------------------------------------------------
// QUICK ACTIONS (updated to use the single card)
//---------------------------------------------------------------------------
Widget _quickActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle("Quick Action"),
_conditionalQuickActionCard(),
],
);
}
//---------------------------------------------------------------------------
// PROJECT DROPDOWN (clean compact)
//---------------------------------------------------------------------------
Widget _projectSelector() {
return Obx(() {
final isLoading = projectController.isLoading.value;
final expanded = projectController.isProjectSelectionExpanded.value;
final projects = projectController.projects;
final selectedId = projectController.selectedProjectId.value;
if (isLoading) {
// Use skeleton instead of CircularProgressIndicator
return SkeletonLoaders.dashboardCardsSkeleton(
maxWidth: MediaQuery.of(context).size.width);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle("Project"),
// Compact Selector
GestureDetector(
onTap: () => projectController.isProjectSelectionExpanded.toggle(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
border: Border.all(color: Colors.black12.withOpacity(.15)),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(.04),
blurRadius: 6,
offset: const Offset(0, 2),
)
],
),
child: Row(
children: [
const Icon(Icons.work_outline, color: Colors.blue, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(
projects
.firstWhereOrNull((p) => p.id == selectedId)
?.name ??
"Select Project",
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w600),
),
),
Icon(
expanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
size: 26,
color: Colors.black54,
)
],
),
),
),
if (expanded) _projectDropdownList(projects, selectedId),
],
);
});
}
//---------------------------------------------------------------------------
// DASHBOARD MODULE CARDS (UPDATED FOR MINIMAL PADDING / SLL SIZE)
//---------------------------------------------------------------------------
Widget _dashboardCards() {
return Obx(() {
if (menuController.isLoading.value) {
// Show skeleton instead of CircularProgressIndicator
return SkeletonLoaders.dashboardCardsSkeleton(
maxWidth: MediaQuery.of(context).size.width);
}
final projectSelected = projectController.selectedProject != null;
final cardOrder = [
MenuItems.attendance,
MenuItems.employees,
MenuItems.dailyTaskPlanning,
MenuItems.dailyProgressReport,
MenuItems.directory,
MenuItems.finance,
MenuItems.documents,
MenuItems.serviceProjects,
];
final meta = {
MenuItems.attendance:
_DashboardCardMeta(LucideIcons.scan_face, contentTheme.success),
MenuItems.employees:
_DashboardCardMeta(LucideIcons.users, contentTheme.warning),
MenuItems.dailyTaskPlanning:
_DashboardCardMeta(LucideIcons.logs, contentTheme.info),
MenuItems.dailyProgressReport:
_DashboardCardMeta(LucideIcons.list_todo, contentTheme.info),
MenuItems.directory:
_DashboardCardMeta(LucideIcons.folder, contentTheme.info),
MenuItems.finance:
_DashboardCardMeta(LucideIcons.wallet, contentTheme.info),
MenuItems.documents:
_DashboardCardMeta(LucideIcons.file_text, contentTheme.info),
MenuItems.serviceProjects:
_DashboardCardMeta(LucideIcons.package, contentTheme.info),
};
final allowed = {
for (var m in menuController.menuItems)
if (m.available && meta.containsKey(m.id)) m.id: m
};
final filtered = cardOrder.where(allowed.containsKey).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionTitle("Modules"),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
// **More compact grid**
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 6,
mainAxisSpacing: 6,
childAspectRatio: 1.2, // smaller & tighter
),
itemCount: filtered.length,
itemBuilder: (context, index) {
final id = filtered[index];
final item = allowed[id]!;
final cardMeta = meta[id]!;
final isEnabled =
item.name == "Attendance" ? true : projectSelected;
return GestureDetector(
onTap: () {
if (!isEnabled) {
Get.defaultDialog(
title: "No Project Selected",
middleText: "Please select a project first.",
);
} else {
Get.toNamed(item.mobileLink);
}
},
child: Container(
// **Reduced padding**
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: isEnabled ? Colors.white : Colors.grey.shade100,
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Colors.black12.withOpacity(.1),
width: 0.7,
),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(.05),
blurRadius: 4,
offset: const Offset(0, 2),
)
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
cardMeta.icon,
size: 20,
color:
isEnabled ? cardMeta.color : Colors.grey.shade400,
),
const SizedBox(height: 3),
Text(
item.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 9.5,
fontWeight: FontWeight.w600,
color:
isEnabled ? Colors.black87 : Colors.grey.shade600,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
],
);
});
}
Widget _projectDropdownList(projects, selectedId) {
return Container(
margin: const EdgeInsets.only(top: 10),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
border: Border.all(color: Colors.black12.withOpacity(.2)),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(.07),
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
constraints:
BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.33),
child: Column(
children: [
TextField(
decoration: InputDecoration(
hintText: "Search project...",
isDense: true,
prefixIcon: const Icon(Icons.search),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(5)),
),
),
const SizedBox(height: 10),
Expanded(
child: ListView.builder(
itemCount: projects.length,
itemBuilder: (_, index) {
final project = projects[index];
return RadioListTile<String>(
dense: true,
value: project.id,
groupValue: selectedId,
onChanged: (v) {
if (v != null) {
projectController.updateSelectedProject(v);
projectController.isProjectSelectionExpanded.value =
false;
}
},
title: Text(project.name),
);
},
),
),
],
),
);
}
//---------------------------------------------------------------------------
// MAIN UI
//---------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xfff5f6fa),
body: Layout(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_projectSelector(),
// MySpacing.height(20),
// _quickActions(),
MySpacing.height(20),
// The updated module cards
_dashboardCards(),
MySpacing.height(20),
_sectionTitle("Reports & Analytics"),
_cardWrapper(child: ExpenseTypeReportChart()),
_cardWrapper(
child:
ExpenseByStatusWidget(controller: dashboardController)),
_cardWrapper(child: MonthlyExpenseDashboardChart()),
MySpacing.height(20),
],
),
),
),
);
}
}
class _DashboardCardMeta {
final IconData icon;
final Color color;
_DashboardCardMeta(this.icon, this.color);
}