added dashboard page #4
@ -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();
|
||||
|
@ -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),
|
||||
);
|
||||
|
@ -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'},
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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()),
|
||||
|
155
lib/view/dashboard/dashboard_screen.dart
Normal file
155
lib/view/dashboard/dashboard_screen.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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'),
|
||||
],
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user