diff --git a/lib/controller/dashboard/attendance_controller.dart b/lib/controller/dashboard/attendance_controller.dart deleted file mode 100644 index 51b1b67..0000000 --- a/lib/controller/dashboard/attendance_controller.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:marco/controller/my_controller.dart'; -import 'package:marco/helpers/widgets/my_text_utils.dart'; -import 'package:marco/model/chart_model.dart'; -import 'package:marco/model/job_recent_application_model.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -class AttendanceController extends MyController { - int isSelectedListingPerformanceTime = 0; - List? chartData; - TooltipBehavior? columnToolTip; - List recentApplication = []; - List dummyTexts = List.generate(12, (index) => MyTextUtils.getDummyText(60)); - - @override - void onInit() { - chartData = [ - ChartSampleData(x: 'Jan', y: 4, secondSeriesYValue: 8), - ChartSampleData(x: 'Feb', y: 9, secondSeriesYValue: 7), - ChartSampleData(x: 'Mar', y: 6, secondSeriesYValue: 5), - ChartSampleData(x: 'Apr', y: 8, secondSeriesYValue: 3), - ChartSampleData(x: 'May', y: 7, secondSeriesYValue: 9), - ChartSampleData(x: 'Jun', y: 10, secondSeriesYValue: 6), - ChartSampleData(x: 'Jul', y: 5, secondSeriesYValue: 4), - ChartSampleData(x: 'Aug', y: 3, secondSeriesYValue: 2), - ChartSampleData(x: 'Sep', y: 6, secondSeriesYValue: 10), - ChartSampleData(x: 'Oct', y: 4, secondSeriesYValue: 8), - ChartSampleData(x: 'Nov', y: 9, secondSeriesYValue: 6), - ChartSampleData(x: 'Dec', y: 7, secondSeriesYValue: 5), - ]; - columnToolTip = TooltipBehavior(enable: true); - JobRecentApplicationModel.dummyList.then((value) { - recentApplication = value.sublist(0, 5); - update(); - }); - super.onInit(); - } - - void onSelectListingPerformanceTimeToggle(index) { - isSelectedListingPerformanceTime = index; - update(); - } -} diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart new file mode 100644 index 0000000..20c13b4 --- /dev/null +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -0,0 +1,56 @@ +import 'package:get/get.dart'; +import 'package:marco/helpers/services/api_service.dart'; +import 'package:marco/model/attendance_model.dart'; +import 'package:marco/model/project_model.dart'; // Assuming you have a ProjectModel for the projects. +import 'package:marco/model/employee_model.dart'; // Assuming you have an EmployeeModel for the employees. + +class AttendanceController extends GetxController { + List attendances = []; + List projects = []; // List of projects + String? selectedProjectId; // Currently selected project ID + List employees = []; // Employees of the selected project + + @override + void onInit() { + super.onInit(); + fetchProjects(); // Fetch projects when initializing + } + + // Fetch projects from API +Future fetchProjects() async { + var response = await ApiService.getProjects(); // Call the project API + + if (response != null) { + projects = response + .map((json) => ProjectModel.fromJson(json)) + .toList(); + + // Set default to the first project if available + if (projects.isNotEmpty) { + selectedProjectId = projects.first.id.toString(); + await fetchEmployeesByProject(selectedProjectId); // Fetch employees for the first project + } + + update(['attendance_dashboard_controller']); // Notify GetBuilder with your tag + } else { + print("No projects data found or failed to fetch data."); + } +} + + + // Fetch employees by project ID + Future fetchEmployeesByProject(String? projectId) async { + if (projectId == null) return; + + var response = await ApiService.getEmployeesByProject(int.parse(projectId)); + + if (response != null) { + employees = response + .map((json) => EmployeeModel.fromJson(json)) + .toList(); + update(); // Trigger UI rebuild + } else { + print("Failed to fetch employees for project $projectId."); + } + } +} diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart new file mode 100644 index 0000000..cf130cc --- /dev/null +++ b/lib/helpers/services/api_service.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:marco/helpers/services/storage/local_storage.dart'; + +class ApiService { + static const String baseUrl = "https://api.marcoaiot.com/api"; + + // Fetch the list of projects + static Future?> getProjects() async { + try { + String? jwtToken = LocalStorage.getJwtToken(); + if (jwtToken == null) { + print("No JWT token found. Please log in."); + return null; + } + + final response = await http.get( + Uri.parse("$baseUrl/project/list"), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $jwtToken', // Add Authorization header + }, + ); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + print("Response body: ${response.body}"); + if (json['success'] == true) { + return json['data']; // Return the data if success is true + } else { + print("Error: ${json['message']}"); + } + } else { + print("Error fetching projects: ${response.statusCode}"); + print("Response body: ${response.body}"); + } + } catch (e) { + print("Exception while fetching projects: $e"); + } + return null; + } + + // Fetch employees by project ID + static Future?> getEmployeesByProject(int projectId) async { + try { + String? jwtToken = LocalStorage.getJwtToken(); + if (jwtToken == null) { + print("No JWT token found. Please log in."); + return null; + } + + final response = await http.get( + Uri.parse("$baseUrl/attendance/project/team?projectId=$projectId"), // Ensure correct endpoint + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $jwtToken', + }, + ); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + print("Response body: ${response.body}"); + if (json['success'] == true) { + return json['data']; // Return employee data + } else { + print("Error: ${json['message']}"); + } + } else { + print("Error fetching employees: ${response.statusCode}"); + print("Response body: ${response.body}"); + } + } catch (e) { + print("Exception while fetching employees: $e"); + } + return null; + } +} diff --git a/lib/helpers/services/auth_service.dart b/lib/helpers/services/auth_service.dart index 9e2b706..c9ef4d5 100644 --- a/lib/helpers/services/auth_service.dart +++ b/lib/helpers/services/auth_service.dart @@ -5,8 +5,7 @@ import 'package:marco/helpers/services/storage/local_storage.dart'; class AuthService { static bool isLoggedIn = false; - static Future?> loginUser( - Map data) async { + static Future?> loginUser(Map data) async { try { final response = await http.post( Uri.parse('https://api.marcoaiot.com/api/auth/login'), @@ -16,9 +15,29 @@ class AuthService { if (response.statusCode == 200) { isLoggedIn = true; + + // Parse the response to get the JWT and refresh tokens + final responseData = jsonDecode(response.body); + + // Adjusted for the actual response structure + final jwtToken = responseData['data']['token']; // Ensure this matches your actual response + + // Save the JWT token in local storage + await LocalStorage.setJwtToken(jwtToken); + print("JWT Token: $jwtToken"); + + // Optionally save refresh token if available + final refreshToken = responseData['data']['refreshToken']; + if (refreshToken != null) { + await LocalStorage.setRefreshToken(refreshToken); + print("Refresh Token: $refreshToken"); + } + + // Save the login state in local storage await LocalStorage.setLoggedInUser(true); - // You can also store the user details in local storage if needed - return null; // No error, login successful + + // Return null to indicate success + return null; } else if (response.statusCode == 401) { return {"password": "Invalid email or password"}; } else { diff --git a/lib/helpers/services/storage/local_storage.dart b/lib/helpers/services/storage/local_storage.dart index 7718c10..1408585 100644 --- a/lib/helpers/services/storage/local_storage.dart +++ b/lib/helpers/services/storage/local_storage.dart @@ -7,6 +7,8 @@ class LocalStorage { static const String _loggedInUserKey = "user"; static const String _themeCustomizerKey = "theme_customizer"; static const String _languageKey = "lang_code"; + static const String _jwtTokenKey = "jwt_token"; + static const String _refreshTokenKey = "refresh_token"; static SharedPreferences? _preferencesInstance; @@ -47,4 +49,34 @@ class LocalStorage { static Future removeLoggedInUser() async { return preferences.remove(_loggedInUserKey); } + + // Add methods to handle JWT and Refresh Token + static Future setToken(String key, String token) { + return preferences.setString(key, token); + } + + static String? getToken(String key) { + return preferences.getString(key); + } + + static Future removeToken(String key) { + return preferences.remove(key); + } + + // Convenience methods for getting the JWT and Refresh tokens + static String? getJwtToken() { + return getToken(_jwtTokenKey); + } + + static String? getRefreshToken() { + return getToken(_refreshTokenKey); + } + + static Future setJwtToken(String jwtToken) { + return setToken(_jwtTokenKey, jwtToken); + } + + static Future setRefreshToken(String refreshToken) { + return setToken(_refreshTokenKey, refreshToken); + } } diff --git a/lib/model/attendance_model.dart b/lib/model/attendance_model.dart new file mode 100644 index 0000000..f12657c --- /dev/null +++ b/lib/model/attendance_model.dart @@ -0,0 +1,37 @@ +class AttendanceModel { + final int id; + final String name; + final String projectAddress; + final String contactPerson; + final DateTime startDate; + final DateTime endDate; + final int teamSize; + final int completedWork; + final int plannedWork; + + AttendanceModel({ + required this.id, + required this.name, + required this.projectAddress, + required this.contactPerson, + required this.startDate, + required this.endDate, + required this.teamSize, + required this.completedWork, + required this.plannedWork, + }); + +factory AttendanceModel.fromJson(Map json) { + return AttendanceModel( + id: int.tryParse(json['id'].toString()) ?? 0, + name: json['name'] ?? '', + projectAddress: json['projectAddress'] ?? '', + contactPerson: json['contactPerson'] ?? '', + startDate: DateTime.tryParse(json['startDate'].toString()) ?? DateTime.now(), + endDate: DateTime.tryParse(json['endDate'].toString()) ?? DateTime.now(), + teamSize: int.tryParse(json['teamSize'].toString()) ?? 0, + completedWork: int.tryParse(json['completedWork'].toString()) ?? 0, + plannedWork: int.tryParse(json['plannedWork'].toString()) ?? 0, + ); +} +} diff --git a/lib/model/employee_model.dart b/lib/model/employee_model.dart new file mode 100644 index 0000000..f73a24c --- /dev/null +++ b/lib/model/employee_model.dart @@ -0,0 +1,28 @@ +class EmployeeModel { + final int id; + final String name; + final String designation; + final String checkIn; + final String checkOut; + final int actions; + + EmployeeModel({ + required this.id, + required this.name, + required this.designation, + required this.checkIn, + required this.checkOut, + required this.actions, + }); + + factory EmployeeModel.fromJson(Map json) { + return EmployeeModel( + id: json['employeeId'] ?? 0, + name: '${json['firstName']} ${json['lastName']}', + designation: json['jobRoleName'] ?? '', + checkIn: json['checkIn'] ?? '-', // Make sure your API returns this field + checkOut: json['checkOut'] ?? '-', + actions: json['actions'] ?? 0, // Make sure your API returns this field + ); + } +} diff --git a/lib/model/project_model.dart b/lib/model/project_model.dart new file mode 100644 index 0000000..dc020fd --- /dev/null +++ b/lib/model/project_model.dart @@ -0,0 +1,62 @@ +class ProjectModel { + final int id; // Unique identifier for the project + final String name; // Name of the project + final String projectAddress; // Address of the project + final String contactPerson; // Contact person for the project + final DateTime startDate; // Start date of the project + final DateTime endDate; // End date of the project + final int teamSize; // Number of people in the team + final int completedWork; // Completed work percentage + final int plannedWork; // Planned work for the project + final int projectStatusId; // Status ID for the project + final int tenantId; // Tenant ID associated with the project + + // Constructor + ProjectModel({ + required this.id, + required this.name, + required this.projectAddress, + required this.contactPerson, + required this.startDate, + required this.endDate, + required this.teamSize, + required this.completedWork, + required this.plannedWork, + required this.projectStatusId, + required this.tenantId, + }); + + // Factory method to create an instance of ProjectModel from a JSON object + factory ProjectModel.fromJson(Map json) { + return ProjectModel( + id: json['id'], + name: json['name'], + projectAddress: json['projectAddress'], + contactPerson: json['contactPerson'], + startDate: DateTime.parse(json['startDate']), + endDate: DateTime.parse(json['endDate']), + teamSize: json['teamSize'], + completedWork: json['completedWork'], + plannedWork: json['plannedWork'], + projectStatusId: json['projectStatusId'], + tenantId: json['tenantId'], + ); + } + + // Method to convert the ProjectModel instance back to a JSON object + Map toJson() { + return { + 'id': id, + 'name': name, + 'projectAddress': projectAddress, + 'contactPerson': contactPerson, + 'startDate': startDate.toIso8601String(), + 'endDate': endDate.toIso8601String(), + 'teamSize': teamSize, + 'completedWork': completedWork, + 'plannedWork': plannedWork, + 'projectStatusId': projectStatusId, + 'tenantId': tenantId, + }; + } +} diff --git a/lib/res/assets_res.dart b/lib/res/assets_res.dart new file mode 100644 index 0000000..f055fc7 --- /dev/null +++ b/lib/res/assets_res.dart @@ -0,0 +1,11 @@ +// Generated file. Do not edit. +// This file is generated by the iFlutter + +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars +class AssetsRes { + AssetsRes._(); + + static const String PROJECT_NAME = 'marco'; + static const String PROJECT_VERSION = '1.0.0+1'; +} diff --git a/lib/routes.dart b/lib/routes.dart index 8789773..9fb7aec 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -9,8 +9,8 @@ import 'package:marco/view/dashboard/ecommerce_screen.dart'; import 'package:marco/view/error_pages/coming_soon_screen.dart'; 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/attendance_screen.dart'; +import 'package:marco/view/dashboard/attendanceScreen.dart'; class AuthMiddleware extends GetMiddleware { @override RouteSettings? redirect(String? route) { @@ -23,7 +23,7 @@ getPageRoute() { GetPage(name: '/', page: () => const EcommerceScreen(), middlewares: [AuthMiddleware()]), // Dashboard - GetPage(name: '/dashboard/attendance', page: () => const AttendanceScreen(), middlewares: [AuthMiddleware()]), + GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]), // Authentication GetPage(name: '/auth/login', page: () => LoginScreen()), diff --git a/lib/view/dashboard/attendanceScreen.dart b/lib/view/dashboard/attendanceScreen.dart new file mode 100644 index 0000000..e2f70cf --- /dev/null +++ b/lib/view/dashboard/attendanceScreen.dart @@ -0,0 +1,200 @@ +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/utils/utils.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_flex.dart'; +import 'package:marco/helpers/widgets/my_flex_item.dart'; +import 'package:marco/helpers/widgets/my_list_extension.dart'; +import 'package:marco/helpers/widgets/my_spacing.dart'; +import 'package:marco/helpers/widgets/my_text.dart'; +import 'package:marco/view/layouts/layout.dart'; +import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; + +class AttendanceScreen extends StatefulWidget { + const AttendanceScreen({super.key}); + + @override + State createState() => _AttendanceScreenState(); +} + +class _AttendanceScreenState extends State with UIMixin { + AttendanceController attendanceController = Get.put(AttendanceController()); + + @override + Widget build(BuildContext context) { + return Layout( + child: GetBuilder( + init: attendanceController, + tag: 'attendance_dashboard_controller', + builder: (controller) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: MySpacing.x(flexSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.titleMedium("Attendance", + fontSize: 18, fontWeight: 600), + MyBreadcrumb( + children: [ + MyBreadcrumbItem(name: 'Dashboard'), + MyBreadcrumbItem(name: 'Attendance', active: true), + ], + ), + ], + ), + ), + MySpacing.height(flexSpacing), + Padding( + padding: MySpacing.x(flexSpacing / 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MySpacing.height(flexSpacing), + MyFlex( + children: [ + MyFlexItem(child: attendanceTableCard()), + ], + ), + ], + ), + ), + ], + ); + }, + ), + ); + } + + Widget attendanceTableCard() { + return MyCard.bordered( + borderRadiusAll: 4, + border: Border.all(color: Colors.grey.withAlpha(50)), + shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), + paddingAll: 24, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: MyContainer.bordered( + padding: MySpacing.xy(8, 4), + child: PopupMenuButton( + onSelected: (value) { + setState(() { + attendanceController.selectedProjectId = value; + attendanceController.fetchEmployeesByProject(value); + }); + }, + itemBuilder: (BuildContext context) { + return attendanceController.projects.map((project) { + return PopupMenuItem( + value: project.id.toString(), + height: 32, + child: MyText.bodySmall( + project.name, + color: theme.colorScheme.onSurface, + fontWeight: 600, + ), + ); + }).toList(); + }, + color: theme.cardTheme.color, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.labelSmall( + attendanceController.selectedProjectId != null + ? attendanceController.projects + .firstWhereOrNull((proj) => + proj.id.toString() == + attendanceController + .selectedProjectId) + ?.name ?? + 'Select a Project' + : 'Select a Project', + color: theme.colorScheme.onSurface, + ), + Icon(LucideIcons.chevron_down, + size: 16, color: theme.colorScheme.onSurface), + ], + ), + ), + ), + ), + ], + ), + MySpacing.height(24), + attendanceController.employees.isEmpty + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DataTable( + sortAscending: true, + columnSpacing: 88, + onSelectAll: (_) => {}, + headingRowColor: WidgetStatePropertyAll( + contentTheme.primary.withAlpha(40)), + dataRowMaxHeight: 60, + showBottomBorder: true, + clipBehavior: Clip.antiAliasWithSaveLayer, + border: TableBorder.all( + borderRadius: BorderRadius.circular(4), + style: BorderStyle.solid, + width: 0.4, + color: Colors.grey, + ), + columns: [ + DataColumn( + label: MyText.labelLarge('ID', + color: contentTheme.primary)), + DataColumn( + label: MyText.labelLarge('Name', + color: contentTheme.primary)), + DataColumn( + label: MyText.labelLarge('Designation', + color: contentTheme.primary)), + DataColumn( + label: MyText.labelLarge('Check In', + color: contentTheme.primary)), + DataColumn( + label: MyText.labelLarge('Check Out', + color: contentTheme.primary)), + DataColumn( + label: MyText.labelLarge('Actions', + color: contentTheme.primary)), + ], + rows: attendanceController.employees + .mapIndexed((index, employee) => DataRow(cells: [ + DataCell(MyText.bodyMedium(employee.id.toString(), + fontWeight: 600)), + DataCell(MyText.bodyMedium(employee.name, + fontWeight: 600)), + DataCell(MyText.bodyMedium(employee.designation, + fontWeight: 600)), + DataCell(MyText.bodyMedium(employee.checkIn, + fontWeight: 600)), + DataCell(MyText.bodyMedium(employee.checkOut, + fontWeight: 600)), + DataCell(MyText.bodyMedium(employee.actions.toString(), + fontWeight: 600)), + ])) + .toList(), + ), + ), + ], + ), + ); + } +} diff --git a/lib/view/dashboard/attendance_screen.dart b/lib/view/dashboard/attendance_screen.dart deleted file mode 100644 index d81c95f..0000000 --- a/lib/view/dashboard/attendance_screen.dart +++ /dev/null @@ -1,435 +0,0 @@ -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/utils/utils.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_flex.dart'; -import 'package:marco/helpers/widgets/my_flex_item.dart'; -import 'package:marco/helpers/widgets/my_list_extension.dart'; -import 'package:marco/helpers/widgets/my_spacing.dart'; -import 'package:marco/helpers/widgets/my_text.dart'; -import 'package:marco/images.dart'; -import 'package:marco/model/chart_model.dart'; -import 'package:marco/view/layouts/layout.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:marco/controller/dashboard/attendance_controller.dart'; -class AttendanceScreen extends StatefulWidget { - const AttendanceScreen({super.key}); - - @override - State createState() => _AttendanceScreenState(); -} - -class _AttendanceScreenState extends State with UIMixin { - AttendanceController controller = Get.put(AttendanceController()); - - @override - Widget build(BuildContext context) { - return Layout( - child: GetBuilder( - init: controller, - tag: 'attendance_dashboard_controller', - builder: (controller) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: MySpacing.x(flexSpacing), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MyText.titleMedium("Attendance", fontSize: 18, fontWeight: 600), - MyBreadcrumb( - children: [ - MyBreadcrumbItem(name: 'Dashboard'), - MyBreadcrumbItem(name: 'Attendance', active: true), - ], - ), - ], - ), - ), - MySpacing.height(flexSpacing), - Padding( - padding: MySpacing.x(flexSpacing / 2), - child: MyFlex( - children: [ - MyFlexItem(sizes: 'xxl-2 xl-4 lg-4 md-4 sm-6', child: stats(LucideIcons.briefcase, '245', 'EMPLOYEES IN SYSTEM', contentTheme.primary)), - MyFlexItem(sizes: 'xxl-2 xl-4 lg-4 md-4 sm-6', child: stats(LucideIcons.file_text, '3201', 'CANDIDATES IN DATA', contentTheme.secondary)), - MyFlexItem(sizes: 'xxl-2 xl-4 lg-4 md-4 sm-6', child: stats(LucideIcons.map_pin, '56', 'LOCATIONS SERVED', contentTheme.success)), - MyFlexItem(sizes: 'xxl-2 xl-4 lg-4 md-4 sm-6', child: stats(LucideIcons.user_plus, '312', 'RECRUITER NETWORK', contentTheme.info)), - MyFlexItem(sizes: 'xxl-2 xl-4 lg-4 md-4 sm-6', child: stats(LucideIcons.credit_card, '689', 'ACTIVE SUBSCRIPTIONS', contentTheme.purple)), - MyFlexItem(sizes: 'xxl-2 xl-4 lg-4 md-4 sm-6', child: stats(LucideIcons.cloud_upload, '82%', 'RESUME UPLOAD RATE', contentTheme.pink)), - MyFlexItem(sizes: 'lg-4', child: workingFormat()), - MyFlexItem(sizes: 'lg-8 md-6', child: listingPerformance()), - MyFlexItem(sizes: 'lg-4 md-6', child: recentCandidate()), - MyFlexItem(sizes: 'lg-4 md-6', child: mostViewedCVs()), - MyFlexItem(sizes: 'lg-4 md-6', child: recentChat()), - MyFlexItem(child: recentApplication()), - ], - )), - ], - ); - }, - ), - ); - } - - Widget stats(IconData? icon, String title, String subTitle, Color color) { - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - child: Row( - children: [ - MyContainer( - paddingAll: 12, - color: color, - child: Icon(icon, color: contentTheme.light, size: 16), - ), - MySpacing.width(16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.titleMedium(title, fontWeight: 600), - MySpacing.height(4), - MyText.labelSmall(subTitle, xMuted: true, maxLines: 1, overflow: TextOverflow.ellipsis), - ], - ), - ) - ], - ), - ); - } - - Widget workingFormat() { - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - height: 408, - child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - MyText.bodyMedium("Working Format", height: .8, fontWeight: 600), - SfCircularChart(legend: Legend(isVisible: true, position: LegendPosition.bottom, overflowMode: LegendItemOverflowMode.wrap), series: [ - DoughnutSeries( - explode: true, - dataSource: [ - ChartSampleData(x: 'OnSite', y: 55, text: '55%'), - ChartSampleData(x: 'Remote', y: 31, text: '31%'), - ChartSampleData(x: 'Hybrid', y: 7.7, text: '7.7%'), - ], - xValueMapper: (ChartSampleData data, _) => data.x as String, - yValueMapper: (ChartSampleData data, _) => data.y, - dataLabelMapper: (ChartSampleData data, _) => data.text, - dataLabelSettings: DataLabelSettings(isVisible: true)) - ]) - ]), - ); - } - - Widget listingPerformance() { - Widget isSelectTime(String title, int index) { - bool isSelect = controller.isSelectedListingPerformanceTime == index; - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 0, position: MyShadowPosition.bottom), - paddingAll: 4, - color: isSelect ? contentTheme.secondary.withValues(alpha:0.15) : null, - onTap: () => controller.onSelectListingPerformanceTimeToggle(index), - child: MyText.labelSmall(title, fontWeight: 600, color: isSelect ? contentTheme.secondary : null), - ); - } - - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: MyText.bodyMedium("Listing Performance", fontWeight: 600, overflow: TextOverflow.ellipsis), - ), - isSelectTime("Day", 0), - MySpacing.width(12), - isSelectTime("Week", 1), - MySpacing.width(12), - isSelectTime("Month", 2), - ], - ), - MySpacing.height(24), - SizedBox( - height: 310, - child: SfCartesianChart( - margin: MySpacing.zero, - plotAreaBorderWidth: 0, - primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)), - primaryYAxis: NumericAxis( - maximum: 20, - minimum: 0, - interval: 4, - axisLine: AxisLine(width: 0), - majorTickLines: MajorTickLines(size: 0), - ), - series: [ - ColumnSeries( - width: .7, - spacing: .2, - dataSource: controller.chartData, - color: theme.colorScheme.primary, - xValueMapper: (ChartSampleData sales, _) => sales.x as String, - yValueMapper: (ChartSampleData sales, _) => sales.y, - name: 'Views'), - ColumnSeries( - dataSource: controller.chartData, - width: .7, - spacing: .2, - color: theme.colorScheme.secondary, - xValueMapper: (ChartSampleData sales, _) => sales.x as String, - yValueMapper: (ChartSampleData sales, _) => sales.secondSeriesYValue, - name: 'Application') - ], - legend: Legend(isVisible: true, position: LegendPosition.bottom), - tooltipBehavior: controller.columnToolTip), - ) - ], - ), - ); - } - - Widget recentCandidate() { - Widget candidatesData(String image, title, subtitle) { - return Row( - children: [ - MyContainer.rounded( - paddingAll: 0, - height: 44, - width: 44, - clipBehavior: Clip.antiAliasWithSaveLayer, - child: Image.asset(image, fit: BoxFit.cover), - ), - MySpacing.width(12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium(title, fontWeight: 600), - MyText.bodySmall(subtitle, fontWeight: 600, xMuted: true, maxLines: 1, overflow: TextOverflow.visible) - ], - ), - ) - ], - ); - } - - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium("Recent Candidate", fontWeight: 600), - MySpacing.height(24), - candidatesData(Images.avatars[3], "Sophia Williams", controller.dummyTexts[0]), - MySpacing.height(24), - candidatesData(Images.avatars[4], "Ethan Johnson", controller.dummyTexts[1]), - MySpacing.height(24), - candidatesData(Images.avatars[5], "Olivia Martinez", controller.dummyTexts[2]), - MySpacing.height(24), - candidatesData(Images.avatars[6], "Liam Brown", controller.dummyTexts[3]), - MySpacing.height(24), - candidatesData(Images.avatars[7], "Ava Davis", controller.dummyTexts[4]), - MySpacing.height(24), - candidatesData(Images.avatars[8], "Mason Lee", controller.dummyTexts[5]), - ], - )); - } - - Widget mostViewedCVs() { - Widget cv(String title) { - return Row( - children: [ - MyContainer.rounded( - paddingAll: 0, - height: 44, - width: 44, - clipBehavior: Clip.antiAliasWithSaveLayer, - color: contentTheme.primary.withAlpha(40), - child: Icon(LucideIcons.file_text, color: contentTheme.primary), - ), - MySpacing.width(12), - Expanded(child: MyText.bodyMedium(title, fontWeight: 600, overflow: TextOverflow.ellipsis)), - InkWell(onTap: () {}, child: Icon(LucideIcons.download)) - ], - ); - } - - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium("Most Viewed CV's", fontWeight: 600), - MySpacing.height(24), - cv("Isabella Green"), - MySpacing.height(24), - cv("James Turner"), - MySpacing.height(24), - cv("Charlotte Scott"), - MySpacing.height(24), - cv("Oliver King"), - MySpacing.height(24), - cv("Lucas Carter"), - MySpacing.height(24), - cv("Mia Brooks"), - ], - ), - ); - } - - Widget recentChat() { - Widget chat(String image, name, message) { - return Row( - children: [ - MyContainer.rounded( - paddingAll: 0, - height: 44, - width: 44, - clipBehavior: Clip.antiAliasWithSaveLayer, - child: Image.asset(image, fit: BoxFit.cover), - ), - MySpacing.width(16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium(name, fontWeight: 600), - MyText.labelSmall(message, fontWeight: 600, maxLines: 1, muted: true, overflow: TextOverflow.ellipsis), - ], - )), - MySpacing.width(28), - Icon(LucideIcons.message_square, size: 20) - ], - ); - } - - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - MyText.bodyMedium("Recent Chat", fontWeight: 600), - MySpacing.height(24), - chat(Images.avatars[0], "Sophia", controller.dummyTexts[6]), - MySpacing.height(24), - chat(Images.avatars[1], "Liam", controller.dummyTexts[5]), - MySpacing.height(24), - chat(Images.avatars[2], "Charlotte", controller.dummyTexts[4]), - MySpacing.height(24), - chat(Images.avatars[3], "Oliver", controller.dummyTexts[3]), - MySpacing.height(24), - chat(Images.avatars[4], "Amelia", controller.dummyTexts[2]), - MySpacing.height(24), - chat(Images.avatars[5], "James", controller.dummyTexts[1]) - ])); - } - - Widget recentApplication() { - return MyCard.bordered( - borderRadiusAll: 4, - border: Border.all(color: Colors.grey.withValues(alpha:.2)), - shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), - paddingAll: 24, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium("Recent Application", fontWeight: 600), - MySpacing.height(24), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: DataTable( - sortAscending: true, - columnSpacing: 88, - onSelectAll: (_) => {}, - headingRowColor: WidgetStatePropertyAll(contentTheme.primary.withAlpha(40)), - dataRowMaxHeight: 60, - showBottomBorder: true, - clipBehavior: Clip.antiAliasWithSaveLayer, - border: TableBorder.all(borderRadius: BorderRadius.circular(4), style: BorderStyle.solid, width: .4, color: Colors.grey), - columns: [ - DataColumn(label: MyText.labelLarge('S.No', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Candidate', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Category', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Designation', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Mail', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Location', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Date', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Type', color: contentTheme.primary)), - DataColumn(label: MyText.labelLarge('Action', color: contentTheme.primary)), - ], - rows: controller.recentApplication - .mapIndexed((index, data) => DataRow(cells: [ - DataCell(MyText.bodyMedium("#${data.id}", fontWeight: 600)), - DataCell(Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - MyContainer( - height: 40, - width: 40, - paddingAll: 0, - child: Image.asset(Images.avatars[index % Images.avatars.length], fit: BoxFit.cover), - ), - MySpacing.width(24), - MyText.labelMedium(data.candidate, fontWeight: 600) - ], - )), - DataCell(MyText.labelMedium(data.category, fontWeight: 600)), - DataCell(MyText.labelMedium(data.designation, fontWeight: 600)), - DataCell(MyText.labelMedium(data.mail, fontWeight: 600)), - DataCell(MyText.labelMedium(data.location, fontWeight: 600)), - DataCell(MyText.labelMedium("${Utils.getDateStringFromDateTime(data.date)}", fontWeight: 600)), - DataCell(MyText.labelMedium(data.type, fontWeight: 600)), - DataCell(Row( - children: [ - MyContainer( - onTap: () {}, - color: contentTheme.primary, - paddingAll: 8, - child: Icon(LucideIcons.download, size: 16, color: contentTheme.onPrimary), - ), - MySpacing.width(12), - MyContainer( - onTap: () {}, - color: contentTheme.secondary, - paddingAll: 8, - child: Icon(LucideIcons.pencil, size: 16, color: contentTheme.onPrimary), - ), - ], - )) - ])) - .toList()), - ) - ], - ), - ); - } -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index be27872..0795ff4 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import file_picker +import geolocator_apple import path_provider_foundation import quill_native_bridge_macos import shared_preferences_foundation @@ -13,6 +14,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 8c8a093..ae4dd33 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -185,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -293,6 +301,54 @@ packages: description: flutter source: sdk version: "0.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" + url: "https://pub.dev" + source: hosted + version: "9.0.2" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d + url: "https://pub.dev" + source: hosted + version: "4.6.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + url: "https://pub.dev" + source: hosted + version: "2.3.13" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + url: "https://pub.dev" + source: hosted + version: "4.2.6" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "4f4218f122a6978d0ad655fa3541eea74c67417440b09f0657238810d5af6bdc" + url: "https://pub.dev" + source: hosted + version: "0.1.3" get: dependency: "direct main" description: @@ -525,6 +581,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" platform: dependency: transitive description: @@ -706,6 +810,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -898,6 +1010,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e443141..12a850f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,7 +59,8 @@ dependencies: syncfusion_flutter_calendar: ^28.2.6 syncfusion_flutter_maps: ^28.1.33 http: ^1.2.2 - + geolocator: ^9.0.1 + permission_handler: ^11.3.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 043a96f..b2cbd25 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,17 @@ #include "generated_plugin_registrant.h" #include +#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + GeolocatorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GeolocatorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a95e267..92c9a0d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,8 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + geolocator_windows + permission_handler_windows url_launcher_windows )