added dashboard page
This commit is contained in:
parent
9fbf82b05c
commit
ed0ebbb29b
@ -6,7 +6,7 @@ import 'package:intl/intl.dart';
|
|||||||
|
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
static const String baseUrl = "https://api.marcoaiot.com/api";
|
static const String baseUrl = "https://stageapi.marcoaiot.com/api";
|
||||||
|
|
||||||
// ===== Common Helpers =====
|
// ===== Common Helpers =====
|
||||||
|
|
||||||
@ -105,21 +105,24 @@ class ApiService {
|
|||||||
|
|
||||||
// ===== Upload Image =====
|
// ===== Upload Image =====
|
||||||
|
|
||||||
static Future<bool> uploadAttendanceImage(
|
static Future<bool> uploadAttendanceImage(
|
||||||
int id,
|
int id,
|
||||||
int employeeId,
|
int employeeId,
|
||||||
XFile imageFile,
|
XFile? imageFile,
|
||||||
double latitude,
|
double latitude,
|
||||||
double longitude, {
|
double longitude, {
|
||||||
required String imageName,
|
required String imageName,
|
||||||
required int projectId,
|
required int projectId,
|
||||||
String comment = "",
|
String comment = "",
|
||||||
required int action, // action passed here
|
required int action,
|
||||||
}) async {
|
bool imageCapture = true, // <- add this flag
|
||||||
final token = await _getToken();
|
}) async {
|
||||||
if (token == null) return false;
|
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 bytes = await imageFile.readAsBytes();
|
||||||
final base64Image = base64Encode(bytes);
|
final base64Image = base64Encode(bytes);
|
||||||
final fileSize = await imageFile.length();
|
final fileSize = await imageFile.length();
|
||||||
@ -132,42 +135,47 @@ class ApiService {
|
|||||||
"description": "Employee attendance photo",
|
"description": "Employee attendance photo",
|
||||||
"base64Data": '$base64Image',
|
"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) {
|
static String generateImageName(int employeeId, int count) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
@ -8,7 +8,7 @@ class AuthService {
|
|||||||
static Future<Map<String, String>?> loginUser(Map<String, dynamic> data) async {
|
static Future<Map<String, String>?> loginUser(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.post(
|
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'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(data),
|
body: jsonEncode(data),
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ class PermissionService {
|
|||||||
static Future<List<UserPermission>> fetchPermissions(String token) async {
|
static Future<List<UserPermission>> fetchPermissions(String token) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(
|
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'},
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class MyApp extends StatelessWidget {
|
|||||||
darkTheme: AppTheme.darkTheme,
|
darkTheme: AppTheme.darkTheme,
|
||||||
themeMode: ThemeCustomizer.instance.theme,
|
themeMode: ThemeCustomizer.instance.theme,
|
||||||
navigatorKey: NavigationService.navigatorKey,
|
navigatorKey: NavigationService.navigatorKey,
|
||||||
initialRoute: "/dashboard/attendance",
|
initialRoute: "/dashboard",
|
||||||
getPages: getPageRoute(),
|
getPages: getPageRoute(),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
NavigationService.registerContext(context);
|
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/error_pages/error_500_screen.dart';
|
||||||
// import 'package:marco/view/dashboard/attendance_screen.dart';
|
// import 'package:marco/view/dashboard/attendance_screen.dart';
|
||||||
import 'package:marco/view/dashboard/attendanceScreen.dart';
|
import 'package:marco/view/dashboard/attendanceScreen.dart';
|
||||||
|
import 'package:marco/view/dashboard/dashboard_screen.dart';
|
||||||
class AuthMiddleware extends GetMiddleware {
|
class AuthMiddleware extends GetMiddleware {
|
||||||
@override
|
@override
|
||||||
RouteSettings? redirect(String? route) {
|
RouteSettings? redirect(String? route) {
|
||||||
@ -24,7 +25,7 @@ getPageRoute() {
|
|||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
||||||
|
GetPage(name: '/dashboard', page: () => DashboardScreen(), middlewares: [AuthMiddleware()]),
|
||||||
// Authentication
|
// Authentication
|
||||||
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
||||||
GetPage(name: '/auth/register_account', page: () => const RegisterAccountScreen()),
|
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: [
|
children: [
|
||||||
Divider(),
|
Divider(),
|
||||||
labelWidget("Dashboard"),
|
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'),
|
NavigationItem(iconData: LucideIcons.layout_template, title: "Attendance", isCondensed: isCondensed, route: '/dashboard/attendance'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user