Merge pull request 'added dashboard page' (#4) from Vaibhav_Task-#131 into main

Reviewed-on: #4
This commit is contained in:
vaibhav.surve 2025-04-30 04:48:28 +00:00
commit af868e644b
7 changed files with 216 additions and 52 deletions

View File

@ -6,7 +6,7 @@ import 'package:intl/intl.dart';
class ApiService {
static const String baseUrl = "https://api.marcoaiot.com/api";
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
// ===== Common Helpers =====
@ -105,21 +105,24 @@ class ApiService {
// ===== Upload Image =====
static Future<bool> uploadAttendanceImage(
int id,
int employeeId,
XFile imageFile,
double latitude,
double longitude, {
required String imageName,
required int projectId,
String comment = "",
required int action, // action passed here
}) async {
final token = await _getToken();
if (token == null) return false;
static Future<bool> uploadAttendanceImage(
int id,
int employeeId,
XFile? imageFile,
double latitude,
double longitude, {
required String imageName,
required int projectId,
String comment = "",
required int action,
bool imageCapture = true, // <- add this flag
}) async {
final token = await _getToken();
if (token == null) return false;
try {
try {
Map<String, dynamic>? imageObject;
if (imageCapture && imageFile != null) {
final bytes = await imageFile.readAsBytes();
final base64Image = base64Encode(bytes);
final fileSize = await imageFile.length();
@ -132,42 +135,47 @@ class ApiService {
"description": "Employee attendance photo",
"base64Data": '$base64Image',
};
final now = DateTime.now();
final body = {
"id": id,
"employeeId": employeeId,
"projectId": projectId,
"markTime": DateFormat('hh:mm a').format(now),
"comment": comment,
"action": action,
"date": DateFormat('yyyy-MM-dd').format(now),
"latitude": '$latitude',
"longitude": '$longitude',
"image": imageObject
};
print("Attendance Image Upload Body: $body");
final response = await http.post(
Uri.parse("$baseUrl/attendance/record-image"),
headers: _headers(token),
body: jsonEncode(body),
);
print("Attendance Image Upload Response: ${response.body}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
return true;
} else {
print("Failed to upload image. API Error: ${json['message']}");
}
} catch (e) {
print("Exception during image upload: $e");
}
return false;
final now = DateTime.now();
final body = {
"id": id,
"employeeId": employeeId,
"projectId": projectId,
"markTime": DateFormat('hh:mm a').format(now),
"comment": comment,
"action": action,
"date": DateFormat('yyyy-MM-dd').format(now),
"latitude": '$latitude',
"longitude": '$longitude',
};
if (imageObject != null) {
body["image"] = imageObject;
}
print("Attendance Image Upload Body: $body");
final response = await http.post(
Uri.parse("$baseUrl/attendance/record-image"),
headers: _headers(token),
body: jsonEncode(body),
);
print("Attendance Image Upload Response: ${response.body}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
return true;
} else {
print("Failed to upload image. API Error: ${json['message']}");
}
} catch (e) {
print("Exception during image upload: $e");
}
// ===== Utilities =====
return false;
}
// ===== Utilities =====
static String generateImageName(int employeeId, int count) {
final now = DateTime.now();

View File

@ -8,7 +8,7 @@ class AuthService {
static Future<Map<String, String>?> loginUser(Map<String, dynamic> data) async {
try {
final response = await http.post(
Uri.parse('https://api.marcoaiot.com/api/auth/login'),
Uri.parse('https://stageapi.marcoaiot.com/api/auth/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);

View File

@ -6,7 +6,7 @@ class PermissionService {
static Future<List<UserPermission>> fetchPermissions(String token) async {
try {
final response = await http.get(
Uri.parse('https://api.marcoaiot.com/api/user/profile'),
Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'),
headers: {'Authorization': 'Bearer $token'},
);

View File

@ -40,7 +40,7 @@ class MyApp extends StatelessWidget {
darkTheme: AppTheme.darkTheme,
themeMode: ThemeCustomizer.instance.theme,
navigatorKey: NavigationService.navigatorKey,
initialRoute: "/dashboard/attendance",
initialRoute: "/dashboard",
getPages: getPageRoute(),
builder: (context, child) {
NavigationService.registerContext(context);

View File

@ -11,6 +11,7 @@ import 'package:marco/view/error_pages/error_404_screen.dart';
import 'package:marco/view/error_pages/error_500_screen.dart';
// import 'package:marco/view/dashboard/attendance_screen.dart';
import 'package:marco/view/dashboard/attendanceScreen.dart';
import 'package:marco/view/dashboard/dashboard_screen.dart';
class AuthMiddleware extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
@ -24,7 +25,7 @@ getPageRoute() {
// Dashboard
GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]),
GetPage(name: '/dashboard', page: () => DashboardScreen(), middlewares: [AuthMiddleware()]),
// Authentication
GetPage(name: '/auth/login', page: () => LoginScreen()),
GetPage(name: '/auth/register_account', page: () => const RegisterAccountScreen()),

View File

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:get/get.dart';
import 'package:marco/helpers/theme/app_theme.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/utils/my_shadow.dart';
import 'package:marco/helpers/widgets/my_breadcrumb.dart';
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
import 'package:marco/helpers/widgets/my_card.dart';
import 'package:marco/helpers/widgets/my_container.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/view/layouts/layout.dart';
class DashboardScreen extends StatelessWidget with UIMixin {
DashboardScreen({super.key});
@override
Widget build(BuildContext context) {
return Layout(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: MySpacing.x(flexSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.titleMedium("Dashboard", fontSize: 18, fontWeight: 600),
MyBreadcrumb(children: [MyBreadcrumbItem(name: 'Dashboard')]),
],
),
),
MySpacing.height(flexSpacing),
Padding(
padding: MySpacing.x(flexSpacing / 2),
child: Column(
children: _dashboardStats().map((rowStats) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: rowStats,
);
}).toList(),
),
),
],
),
);
}
List<List<Widget>> _dashboardStats() {
final stats = [
{
"icon": LucideIcons.layout_dashboard,
"title": "Dashboard",
"route": "/dashboard/attendance",
"color": contentTheme.primary
},
{
"icon": LucideIcons.folder,
"title": "Projects",
"route": "/dashboard/attendance",
"color": contentTheme.secondary
},
{
"icon": LucideIcons.check,
"title": "Attendence",
"route": "/dashboard/attendance",
"color": contentTheme.success
},
{
"icon": LucideIcons.users,
"title": "Task",
"route": "/dashboard/attendance",
"color": contentTheme.info
},
];
// Group the stats into pairs for rows
List<List<Widget>> rows = [];
for (int i = 0; i < stats.length; i += 2) {
rows.add([
_buildStatCard(
icon: stats[i]['icon'] as IconData,
title: stats[i]['title'] as String,
route: stats[i]['route'] as String,
color: stats[i]['color'] as Color,
),
if (i + 1 < stats.length) _buildStatCard(
icon: stats[i + 1]['icon'] as IconData,
title: stats[i + 1]['title'] as String,
route: stats[i + 1]['route'] as String,
color: stats[i + 1]['color'] as Color,
),
]);
}
return rows;
}
Widget _buildStatCard({
required IconData icon,
required String title,
required String route,
required Color color,
String count = "",
String change = "",
}) {
return Expanded(
child: InkWell(
onTap: () => Get.toNamed(route),
child: MyCard.bordered(
borderRadiusAll: 10,
border: Border.all(color: Colors.grey.withOpacity(0.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 24,
height: 140,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyContainer(
paddingAll: 16,
color: color.withOpacity(0.2),
child: MyContainer(
paddingAll: 8,
color: color,
child: Icon(icon, size: 16, color: contentTheme.light),
),
),
MySpacing.height(12),
MyText.labelSmall(title, maxLines: 1),
if (count.isNotEmpty)
MyText.bodyMedium(count, fontWeight: 600, maxLines: 1),
if (change.isNotEmpty)
Padding(
padding: MySpacing.top(8),
child: MyContainer(
padding: MySpacing.xy(6, 4),
color: change.startsWith('+')
? Colors.green.withOpacity(0.2)
: theme.colorScheme.error.withOpacity(0.2),
child: MyText.labelSmall(
change,
color: change.startsWith('+')
? Colors.green
: theme.colorScheme.error,
),
),
),
],
),
),
),
);
}
}

View File

@ -91,7 +91,7 @@ class _LeftBarState extends State<LeftBar> with SingleTickerProviderStateMixin,
children: [
Divider(),
labelWidget("Dashboard"),
NavigationItem(iconData: LucideIcons.layout_dashboard, title: "Dashboard", isCondensed: isCondensed, route: '/dashboard'),
NavigationItem(iconData: LucideIcons.layout_template, title: "Attendance", isCondensed: isCondensed, route: '/dashboard/attendance'),
],
),