Feature_Report_Action #48
@ -1,54 +1,100 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:marco/helpers/services/api_service.dart';
|
import 'package:marco/helpers/services/api_service.dart';
|
||||||
import 'package:marco/model/project_model.dart';
|
import 'package:marco/controller/project_controller.dart';
|
||||||
|
|
||||||
final Logger log = Logger();
|
final Logger log = Logger();
|
||||||
|
|
||||||
class DashboardController extends GetxController {
|
class DashboardController extends GetxController {
|
||||||
RxList<ProjectModel> projects = <ProjectModel>[].obs;
|
// Observables
|
||||||
RxString? selectedProjectId;
|
final RxList<Map<String, dynamic>> roleWiseData = <Map<String, dynamic>>[].obs;
|
||||||
var isProjectListExpanded = false.obs;
|
final RxBool isLoading = false.obs;
|
||||||
RxBool isProjectSelectionExpanded = true.obs;
|
final RxString selectedRange = '7D'.obs;
|
||||||
|
final RxBool isChartView = true.obs;
|
||||||
|
|
||||||
void toggleProjectListExpanded() {
|
// Inject the ProjectController
|
||||||
isProjectListExpanded.value = !isProjectListExpanded.value;
|
final ProjectController projectController = Get.find<ProjectController>();
|
||||||
}
|
|
||||||
|
|
||||||
var isProjectDropdownExpanded = false.obs;
|
|
||||||
|
|
||||||
RxBool isLoading = true.obs;
|
|
||||||
RxBool isLoadingProjects = true.obs;
|
|
||||||
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
fetchProjects();
|
|
||||||
|
final selectedProjectIdRx = projectController.selectedProjectId;
|
||||||
|
|
||||||
|
if (selectedProjectIdRx != null) {
|
||||||
|
// Fix: explicitly cast and use ever<T> with non-nullable type
|
||||||
|
ever<String>(selectedProjectIdRx, (id) {
|
||||||
|
if (id.isNotEmpty) {
|
||||||
|
fetchRoleWiseAttendance();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial load if already has value
|
||||||
|
if (selectedProjectIdRx.value.isNotEmpty) {
|
||||||
|
fetchRoleWiseAttendance();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.w('selectedProjectId observable is null in ProjectController.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches projects and initializes selected project.
|
ever(selectedRange, (_) {
|
||||||
Future<void> fetchProjects() async {
|
fetchRoleWiseAttendance();
|
||||||
isLoadingProjects.value = true;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int get rangeDays => _getDaysFromRange(selectedRange.value);
|
||||||
|
|
||||||
|
int _getDaysFromRange(String range) {
|
||||||
|
switch (range) {
|
||||||
|
case '15D':
|
||||||
|
return 15;
|
||||||
|
case '30D':
|
||||||
|
return 30;
|
||||||
|
case '7D':
|
||||||
|
default:
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateRange(String range) {
|
||||||
|
selectedRange.value = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleChartView(bool isChart) {
|
||||||
|
isChartView.value = isChart;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshDashboard() async {
|
||||||
|
await fetchRoleWiseAttendance();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchRoleWiseAttendance() async {
|
||||||
|
final String? projectId = projectController.selectedProjectId?.value;
|
||||||
|
|
||||||
|
if (projectId == null || projectId.isEmpty) {
|
||||||
|
log.w('Project ID is null or empty, skipping API call.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
final response = await ApiService.getProjects();
|
final List<dynamic>? response =
|
||||||
|
await ApiService.getDashboardAttendanceOverview(projectId, rangeDays);
|
||||||
|
|
||||||
if (response != null && response.isNotEmpty) {
|
if (response != null) {
|
||||||
projects.assignAll(
|
roleWiseData.value =
|
||||||
response.map((json) => ProjectModel.fromJson(json)).toList());
|
response.map((e) => Map<String, dynamic>.from(e)).toList();
|
||||||
selectedProjectId = RxString(projects.first.id.toString());
|
log.i('Attendance overview fetched successfully.');
|
||||||
log.i("Projects fetched: ${projects.length}");
|
|
||||||
} else {
|
} else {
|
||||||
log.w("No projects found or API call failed.");
|
log.e('Failed to fetch attendance overview: response is null.');
|
||||||
|
roleWiseData.clear();
|
||||||
}
|
}
|
||||||
|
} catch (e, st) {
|
||||||
isLoadingProjects.value = false;
|
log.e('Error fetching attendance overview', error: e, stackTrace: st);
|
||||||
|
roleWiseData.clear();
|
||||||
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
update(['dashboard_controller']);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void updateSelectedProject(String projectId) {
|
|
||||||
selectedProjectId?.value = projectId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ class ApiEndpoints {
|
|||||||
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
|
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
|
||||||
// static const String baseUrl = "https://api.marcoaiot.com/api";
|
// static const String baseUrl = "https://api.marcoaiot.com/api";
|
||||||
|
|
||||||
|
// Dashboard Screen API Endpoints
|
||||||
|
static const String getDashboardAttendanceOverview = "/dashboard/attendance-overview";
|
||||||
|
|
||||||
// Attendance Screen API Endpoints
|
// Attendance Screen API Endpoints
|
||||||
static const String getProjects = "/project/list";
|
static const String getProjects = "/project/list";
|
||||||
static const String getGlobalProjects = "/project/list/basic";
|
static const String getGlobalProjects = "/project/list/basic";
|
||||||
|
@ -122,6 +122,21 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Dashboard Endpoints ===
|
||||||
|
|
||||||
|
static Future<List<dynamic>?> getDashboardAttendanceOverview(
|
||||||
|
String projectId, int days) async {
|
||||||
|
if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
|
||||||
|
if (days <= 0) throw ArgumentError('days must be greater than 0');
|
||||||
|
|
||||||
|
final endpoint =
|
||||||
|
"${ApiEndpoints.getDashboardAttendanceOverview}/$projectId?days=$days";
|
||||||
|
|
||||||
|
return _getRequest(endpoint).then((res) => res != null
|
||||||
|
? _parseResponse(res, label: 'Dashboard Attendance Overview')
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
|
||||||
// === Attendance APIs ===
|
// === Attendance APIs ===
|
||||||
|
|
||||||
static Future<List<dynamic>?> getProjects() async =>
|
static Future<List<dynamic>?> getProjects() async =>
|
||||||
|
@ -4,6 +4,37 @@ import 'package:marco/helpers/widgets/my_spacing.dart';
|
|||||||
import 'package:marco/helpers/utils/my_shadow.dart';
|
import 'package:marco/helpers/utils/my_shadow.dart';
|
||||||
|
|
||||||
class SkeletonLoaders {
|
class SkeletonLoaders {
|
||||||
|
|
||||||
|
static Widget buildLoadingSkeleton() {
|
||||||
|
return SizedBox(
|
||||||
|
height: 360,
|
||||||
|
child: Column(
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: List.generate(6, (i) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
width: 48,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Employee List - Card Style
|
// Employee List - Card Style
|
||||||
static Widget employeeListSkeletonLoader() {
|
static Widget employeeListSkeletonLoader() {
|
||||||
return Column(
|
return Column(
|
||||||
|
19
lib/model/dashboard/attendance_overview_model.dart
Normal file
19
lib/model/dashboard/attendance_overview_model.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class AttendanceOverview {
|
||||||
|
final String role;
|
||||||
|
final String date;
|
||||||
|
final int present;
|
||||||
|
|
||||||
|
AttendanceOverview({
|
||||||
|
required this.role,
|
||||||
|
required this.date,
|
||||||
|
required this.present,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AttendanceOverview.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AttendanceOverview(
|
||||||
|
role: json['role'] ?? '',
|
||||||
|
date: json['date'] ?? '',
|
||||||
|
present: json['present'] ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
295
lib/view/dashboard/dashboard_chart.dart
Normal file
295
lib/view/dashboard/dashboard_chart.dart
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
import 'package:marco/controller/dashboard/dashboard_controller.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
|
||||||
|
|
||||||
|
class AttendanceDashboardChart extends StatelessWidget {
|
||||||
|
final DashboardController controller = Get.find<DashboardController>();
|
||||||
|
|
||||||
|
AttendanceDashboardChart({super.key});
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get filteredData {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final daysBack = controller.rangeDays;
|
||||||
|
return controller.roleWiseData.where((entry) {
|
||||||
|
final date = DateTime.parse(entry['date']);
|
||||||
|
return date.isAfter(now.subtract(Duration(days: daysBack))) &&
|
||||||
|
!date.isAfter(now);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DateTime> get filteredDateTimes {
|
||||||
|
final uniqueDates = filteredData
|
||||||
|
.map((e) => DateTime.parse(e['date'] as String))
|
||||||
|
.toSet()
|
||||||
|
.toList()
|
||||||
|
..sort();
|
||||||
|
return uniqueDates;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> get filteredDates =>
|
||||||
|
filteredDateTimes.map((d) => DateFormat('d MMMM').format(d)).toList();
|
||||||
|
|
||||||
|
List<String> get filteredRoles =>
|
||||||
|
filteredData.map((e) => e['role'] as String).toSet().toList();
|
||||||
|
|
||||||
|
final Map<String, Color> _roleColorMap = {};
|
||||||
|
final List<Color> flatColors = [
|
||||||
|
const Color(0xFFE57373),
|
||||||
|
const Color(0xFF64B5F6),
|
||||||
|
const Color(0xFF81C784),
|
||||||
|
const Color(0xFFFFB74D),
|
||||||
|
const Color(0xFFBA68C8),
|
||||||
|
const Color(0xFFFF8A65),
|
||||||
|
const Color(0xFF4DB6AC),
|
||||||
|
const Color(0xFFA1887F),
|
||||||
|
const Color(0xFFDCE775),
|
||||||
|
const Color(0xFF9575CD),
|
||||||
|
];
|
||||||
|
|
||||||
|
Color _getRoleColor(String role) {
|
||||||
|
if (_roleColorMap.containsKey(role)) return _roleColorMap[role]!;
|
||||||
|
|
||||||
|
final index = _roleColorMap.length % flatColors.length;
|
||||||
|
final color = flatColors[index];
|
||||||
|
_roleColorMap[role] = color;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final isChartView = controller.isChartView.value;
|
||||||
|
final selectedRange = controller.selectedRange.value;
|
||||||
|
final isLoading = controller.isLoading.value;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Color(0xfff0f4f8), Color(0xffe2ebf0)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Card(
|
||||||
|
color: Colors.white,
|
||||||
|
elevation: 6,
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
shadowColor: Colors.black12,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(selectedRange, isChartView),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: isLoading
|
||||||
|
? SkeletonLoaders.buildLoadingSkeleton()
|
||||||
|
: isChartView
|
||||||
|
? _buildChart()
|
||||||
|
: _buildTable(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(String selectedRange, bool isChartView) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.bodyMedium('Attendance Overview', fontWeight: 600),
|
||||||
|
MyText.bodySmall(
|
||||||
|
'Role-wise present count',
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
PopupMenuButton<String>(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tooltip: 'Select Range',
|
||||||
|
onSelected: (value) => controller.selectedRange.value = value,
|
||||||
|
itemBuilder: (context) => const [
|
||||||
|
PopupMenuItem(value: '7D', child: Text('Last 7 Days')),
|
||||||
|
PopupMenuItem(value: '15D', child: Text('Last 15 Days')),
|
||||||
|
PopupMenuItem(value: '30D', child: Text('Last 30 Days')),
|
||||||
|
],
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.calendar_today_outlined, size: 18),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
MyText.labelSmall(selectedRange),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.bar_chart_rounded,
|
||||||
|
size: 20,
|
||||||
|
color: isChartView ? Colors.blueAccent : Colors.grey,
|
||||||
|
),
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () => controller.isChartView.value = true,
|
||||||
|
tooltip: 'Chart View',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.table_chart,
|
||||||
|
size: 20,
|
||||||
|
color: !isChartView ? Colors.blueAccent : Colors.grey,
|
||||||
|
),
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () => controller.isChartView.value = false,
|
||||||
|
tooltip: 'Table View',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildChart() {
|
||||||
|
final formattedDateMap = {
|
||||||
|
for (var e in filteredData)
|
||||||
|
'${e['role']}_${DateFormat('d MMMM').format(DateTime.parse(e['date']))}':
|
||||||
|
e['present']
|
||||||
|
};
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 360,
|
||||||
|
child: SfCartesianChart(
|
||||||
|
tooltipBehavior: TooltipBehavior(
|
||||||
|
enable: true,
|
||||||
|
shared: true,
|
||||||
|
activationMode: ActivationMode.singleTap,
|
||||||
|
tooltipPosition: TooltipPosition.pointer,
|
||||||
|
),
|
||||||
|
legend: const Legend(
|
||||||
|
isVisible: true,
|
||||||
|
position: LegendPosition.bottom,
|
||||||
|
overflowMode: LegendItemOverflowMode.wrap,
|
||||||
|
),
|
||||||
|
primaryXAxis: CategoryAxis(
|
||||||
|
labelRotation: 45,
|
||||||
|
majorGridLines: const MajorGridLines(width: 0),
|
||||||
|
),
|
||||||
|
primaryYAxis: NumericAxis(
|
||||||
|
minimum: 0,
|
||||||
|
interval: 1,
|
||||||
|
majorGridLines: const MajorGridLines(width: 0),
|
||||||
|
),
|
||||||
|
series: filteredRoles.map((role) {
|
||||||
|
final data = filteredDates.map((formattedDate) {
|
||||||
|
final key = '${role}_$formattedDate';
|
||||||
|
return {
|
||||||
|
'date': formattedDate,
|
||||||
|
'present': formattedDateMap[key] ?? 0
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return StackedColumnSeries<Map<String, dynamic>, String>(
|
||||||
|
dataSource: data,
|
||||||
|
xValueMapper: (d, _) => d['date'],
|
||||||
|
yValueMapper: (d, _) => d['present'],
|
||||||
|
name: role,
|
||||||
|
legendIconType: LegendIconType.circle,
|
||||||
|
dataLabelSettings: const DataLabelSettings(isVisible: true),
|
||||||
|
color: _getRoleColor(role),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTable() {
|
||||||
|
final formattedDateMap = {
|
||||||
|
for (var e in filteredData)
|
||||||
|
'${e['role']}_${DateFormat('d MMMM').format(DateTime.parse(e['date']))}':
|
||||||
|
e['present']
|
||||||
|
};
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DataTable(
|
||||||
|
columnSpacing: 28,
|
||||||
|
headingRowHeight: 42,
|
||||||
|
headingRowColor:
|
||||||
|
WidgetStateProperty.all(Colors.blueAccent.withOpacity(0.1)),
|
||||||
|
headingTextStyle: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, color: Colors.black87),
|
||||||
|
columns: [
|
||||||
|
DataColumn(label: MyText.labelSmall('Role', fontWeight: 600)),
|
||||||
|
...filteredDates.map((date) => DataColumn(
|
||||||
|
label: MyText.labelSmall(date, fontWeight: 600),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
rows: filteredRoles.map((role) {
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: _rolePill(role),
|
||||||
|
)),
|
||||||
|
...filteredDates.map((date) {
|
||||||
|
final key = '${role}_$date';
|
||||||
|
return DataCell(Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: MyText.labelSmall('${formattedDateMap[key] ?? 0}'),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _rolePill(String role) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _getRoleColor(role).withOpacity(0.15),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: MyText.labelSmall(role, fontWeight: 500),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,22 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_lucide/flutter_lucide.dart';
|
import 'package:flutter_lucide/flutter_lucide.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:marco/controller/dashboard/dashboard_controller.dart';
|
||||||
|
import 'package:marco/controller/project_controller.dart';
|
||||||
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
import 'package:marco/helpers/utils/my_shadow.dart';
|
import 'package:marco/helpers/utils/my_shadow.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_button.dart';
|
||||||
import 'package:marco/helpers/widgets/my_card.dart';
|
import 'package:marco/helpers/widgets/my_card.dart';
|
||||||
import 'package:marco/helpers/widgets/my_container.dart';
|
import 'package:marco/helpers/widgets/my_container.dart';
|
||||||
import 'package:marco/helpers/widgets/my_spacing.dart';
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text.dart';
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:marco/view/dashboard/dashboard_chart.dart';
|
||||||
import 'package:marco/view/layouts/layout.dart';
|
import 'package:marco/view/layouts/layout.dart';
|
||||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
|
||||||
import 'package:marco/helpers/widgets/my_button.dart';
|
|
||||||
import 'package:marco/controller/project_controller.dart';
|
|
||||||
|
|
||||||
class DashboardScreen extends StatefulWidget {
|
class DashboardScreen extends StatefulWidget {
|
||||||
const DashboardScreen({super.key});
|
const DashboardScreen({super.key});
|
||||||
|
|
||||||
static const String dashboardRoute = "/dashboard";
|
static const String dashboardRoute = "/dashboard";
|
||||||
static const String employeesRoute = "/dashboard/employees";
|
static const String employeesRoute = "/dashboard/employees";
|
||||||
static const String projectsRoute = "/dashboard";
|
static const String projectsRoute = "/dashboard";
|
||||||
@ -28,6 +31,8 @@ class DashboardScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||||
|
final DashboardController dashboardController =
|
||||||
|
Get.put(DashboardController());
|
||||||
bool hasMpin = true;
|
bool hasMpin = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -53,6 +58,9 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
children: [
|
children: [
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
_buildDashboardStats(),
|
_buildDashboardStats(),
|
||||||
|
MySpacing.height(24),
|
||||||
|
AttendanceDashboardChart(),
|
||||||
|
|
||||||
MySpacing.height(300),
|
MySpacing.height(300),
|
||||||
if (!hasMpin) ...[
|
if (!hasMpin) ...[
|
||||||
MyCard(
|
MyCard(
|
||||||
@ -63,7 +71,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.warning_amber_rounded,
|
const Icon(Icons.warning_amber_rounded,
|
||||||
color: Colors.redAccent, size: 28),
|
color: Colors.redAccent, size: 28),
|
||||||
MySpacing.width(12),
|
MySpacing.width(12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -93,7 +101,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.lock_outline,
|
const Icon(Icons.lock_outline,
|
||||||
size: 18, color: Colors.white),
|
size: 18, color: Colors.white),
|
||||||
MySpacing.width(8),
|
MySpacing.width(8),
|
||||||
MyText.bodyMedium(
|
MyText.bodyMedium(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user