diff --git a/.gitignore b/.gitignore index 29a3a50..5d6fa2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ +# Do not remove or rename entries in this file, only add new ones +# See https://github.com/flutter/flutter/issues/128635 for more context. + # Miscellaneous *.class +*.lock *.log *.pyc *.swp @@ -16,10 +20,46 @@ migrate_working_dir/ *.iws .idea/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/* +.ccls-cache + +# This file, on the master branch, should never exist or be checked-in. +# +# On a *final* release branch, that is, what will ship to stable or beta, the +# file can be force added (git add --force) and checked-in in order to effectively +# "pin" the engine artifact version so the flutter tool does not need to use git +# to determine the engine artifacts. +# +# See https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md. +/bin/internal/engine.version + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/internal/engine.realm +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/api_docs.zip +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated # Flutter/Dart/Pub related **/doc/api/ @@ -27,17 +67,97 @@ migrate_working_dir/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies +**/generated_plugin_registrant.dart +.packages +.pub-preload-cache/ .pub-cache/ .pub/ -/build/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds -# Symbolication related +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks +local.properties +**/.cxx/ + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/ephemeral/ +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/ephemeral/ +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake + +# Coverage +coverage/ + +# Symbols app.*.symbols -# Obfuscation related -app.*.map.json +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/settings.json -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +# Monorepo +.cipd +.gclient +.gclient_entries +.python-version +.gclient_previous_custom_vars +.gclient_previous_sync_commits \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index b9e43bd..a42444d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false + id "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/assets/auth_background.jpg b/assets/auth_background.jpg index 7a75ac9..4ef1982 100644 Binary files a/assets/auth_background.jpg and b/assets/auth_background.jpg differ diff --git a/assets/logo/logo_dark.png b/assets/logo/logo_dark.png index 4049058..ba9c246 100644 Binary files a/assets/logo/logo_dark.png and b/assets/logo/logo_dark.png differ diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index 978112e..9f3059c 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -10,12 +10,12 @@ class LoginController extends MyController { bool showPassword = false, isChecked = false; - final String _dummyEmail = "demo@example.com"; - final String _dummyPassword = "1234567"; + final String _dummyEmail = "admin@marcobms.com"; + final String _dummyPassword = "User@123"; @override void onInit() { - basicValidator.addField('email', required: true, label: "Email", validators: [MyEmailValidator()], controller: TextEditingController(text: _dummyEmail)); + basicValidator.addField('username', required: true, label: "User_Name", validators: [MyEmailValidator()], controller: TextEditingController(text: _dummyEmail)); basicValidator.addField('password', required: true, label: "Password", validators: [MyLengthValidator(min: 6, max: 10)], controller: TextEditingController(text: _dummyPassword)); 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 5ada735..c9ef4d5 100644 --- a/lib/helpers/services/auth_service.dart +++ b/lib/helpers/services/auth_service.dart @@ -1,22 +1,50 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; import 'package:marco/helpers/services/storage/local_storage.dart'; -import 'package:marco/model/user.dart'; class AuthService { static bool isLoggedIn = false; - static User get dummyUser => User(-1, "demo@example.com", "Denish", "Navadiya"); + static Future?> loginUser(Map data) async { + try { + final response = await http.post( + Uri.parse('https://api.marcoaiot.com/api/auth/login'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(data), + ); - static Future?> loginUser( - Map data) async { - await Future.delayed(Duration(seconds: 1)); - if (data['email'] != dummyUser.email) { - return {"email": "This email is not registered"}; - } else if (data['password'] != "1234567") { - return {"password": "Password is incorrect"}; + 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); + + // Return null to indicate success + return null; + } else if (response.statusCode == 401) { + return {"password": "Invalid email or password"}; + } else { + return {"error": "Something went wrong. Please try again."}; + } + } catch (e) { + return {"error": "Network error. Please check your connection."}; } - - isLoggedIn = true; - await LocalStorage.setLoggedInUser(true); - return null; } } 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/model/user.dart b/lib/model/user.dart index f1c11f1..53656f2 100644 --- a/lib/model/user.dart +++ b/lib/model/user.dart @@ -1,9 +1,9 @@ import 'package:marco/model/identifier_model.dart'; class User extends IdentifierModel { - final String email, firstName, lastName; + final String username, firstName, lastName; - User(super.id, this.email, this.firstName, this.lastName); + User(super.id, this.username, this.firstName, this.lastName); String get name => "$firstName $lastName"; 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/auth/login_screen.dart b/lib/view/auth/login_screen.dart index 04e0a6a..eb99c61 100644 --- a/lib/view/auth/login_screen.dart +++ b/lib/view/auth/login_screen.dart @@ -9,6 +9,7 @@ import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text_style.dart'; import 'package:marco/view/layouts/auth_layout.dart'; +import 'package:marco/images.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -17,7 +18,7 @@ class LoginScreen extends StatefulWidget { State createState() => _LoginScreenState(); } -class _LoginScreenState extends State with UIMixin{ +class _LoginScreenState extends State with UIMixin { late LoginController controller; @override @@ -29,114 +30,198 @@ class _LoginScreenState extends State with UIMixin{ @override Widget build(BuildContext context) { return AuthLayout( - child: GetBuilder( + child: GetBuilder( init: controller, tag: 'login_controller', builder: (controller) { - return Form( - key: controller.basicValidator.formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - MyText.titleLarge("Sign in with email", fontWeight: 600), - MySpacing.height(12), - MyText.bodyMedium("Make a new doc to bring your words, data and terms together. For free", fontWeight: 600, xMuted: true), - MySpacing.height(12), - TextFormField( - validator: controller.basicValidator.getValidation('email'), - controller: controller.basicValidator.getController('email'), - keyboardType: TextInputType.emailAddress, - style: MyTextStyle.labelMedium(), - decoration: InputDecoration( - labelText: "Email Address", - labelStyle: MyTextStyle.bodySmall(xMuted: true), - border: OutlineInputBorder(borderSide: BorderSide.none), - filled: true, - fillColor: contentTheme.secondary.withAlpha(36), - prefixIcon: const Icon(LucideIcons.mail, size: 16), - contentPadding: MySpacing.all(14), - isDense: true, - isCollapsed: true, - floatingLabelBehavior: FloatingLabelBehavior.never), - ), - MySpacing.height(20), - TextFormField( - validator: controller.basicValidator.getValidation('password'), - controller: controller.basicValidator.getController('password'), - keyboardType: TextInputType.visiblePassword, - obscureText: !controller.showPassword, - style: MyTextStyle.labelMedium(), - decoration: InputDecoration( - labelText: "Password", - labelStyle: MyTextStyle.bodySmall(xMuted: true), - border: OutlineInputBorder(borderSide: BorderSide.none), - filled: true, - fillColor: contentTheme.secondary.withAlpha(36), - prefixIcon: const Icon(LucideIcons.mail, size: 16), - contentPadding: MySpacing.all(16), - isCollapsed: true, - isDense: true, - floatingLabelBehavior: FloatingLabelBehavior.never, - suffixIcon: InkWell( - onTap: controller.onChangeShowPassword, - child: Icon(controller.showPassword ? LucideIcons.eye : LucideIcons.eye_off, size: 16), - )), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: () => controller.onChangeCheckBox(!controller.isChecked), - child: Row( - children: [ - Checkbox( - onChanged: controller.onChangeCheckBox, - value: controller.isChecked, - fillColor: WidgetStatePropertyAll(Colors.white), - activeColor: theme.colorScheme.primary, - checkColor: contentTheme.primary, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: getCompactDensity, + return Form( + key: controller.basicValidator.formKey, + child: SingleChildScrollView( + padding: MySpacing.xy(2, 40), + child: Container( + width: double.infinity, + padding: MySpacing.all(24), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withOpacity(0.02), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: contentTheme.primary.withOpacity(0.5), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Logo + Center( + child: Image.asset( + Images.logoDark, + height: 120, + fit: BoxFit.contain, + ), + ), + MySpacing.height(20), + + /// Welcome Text + Center( + child: MyText.bodyLarge("Welcome Back!", fontWeight: 600), + ), + MySpacing.height(4), + Center( + child: MyText.bodySmall("Please sign in to continue."), + ), + MySpacing.height(20), + + /// Email Field + MyText.bodySmall("Email Address", fontWeight: 600), + MySpacing.height(8), + Material( + elevation: 2, + shadowColor: contentTheme.secondary.withAlpha(30), + borderRadius: BorderRadius.circular(12), + child: TextFormField( + validator: + controller.basicValidator.getValidation('username'), + controller: + controller.basicValidator.getController('username'), + keyboardType: TextInputType.emailAddress, + style: MyTextStyle.labelMedium(), + decoration: InputDecoration( + hintText: "Enter your email", + hintStyle: MyTextStyle.bodySmall(xMuted: true), + filled: true, + fillColor: theme.cardColor, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(2), + borderSide: BorderSide.none, + ), + prefixIcon: const Icon(LucideIcons.mail, size: 18), + contentPadding: MySpacing.xy(12, 16), + ), + ), + ), + MySpacing.height(16), + + /// Password Field Label + MyText.bodySmall("Password", fontWeight: 600), + MySpacing.height(8), + Material( + elevation: 2, + shadowColor: contentTheme.secondary.withAlpha(25), + borderRadius: BorderRadius.circular(12), + child: TextFormField( + validator: + controller.basicValidator.getValidation('password'), + controller: + controller.basicValidator.getController('password'), + keyboardType: TextInputType.visiblePassword, + obscureText: !controller.showPassword, + style: MyTextStyle.labelMedium(), + decoration: InputDecoration( + hintText: "Enter your password", + hintStyle: MyTextStyle.bodySmall(xMuted: true), + filled: true, + fillColor: theme.cardColor, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(2), + borderSide: BorderSide.none, + ), + prefixIcon: const Icon(LucideIcons.lock, size: 18), + suffixIcon: InkWell( + onTap: controller.onChangeShowPassword, + child: Icon( + controller.showPassword + ? LucideIcons.eye + : LucideIcons.eye_off, + size: 18, + ), + ), + contentPadding: MySpacing.all(3), + ), + ), + ), + + MySpacing.height(16), + + /// Remember Me + Forgot Password + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () => controller + .onChangeCheckBox(!controller.isChecked), + child: Row( + children: [ + Checkbox( + onChanged: controller.onChangeCheckBox, + value: controller.isChecked, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + fillColor: WidgetStatePropertyAll( + contentTheme.secondary), + checkColor: contentTheme.onPrimary, + visualDensity: getCompactDensity, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + ), + MySpacing.width(8), + MyText.bodySmall("Remember Me"), + ], + ), + ), + MyButton.text( + onPressed: controller.goToForgotPassword, + elevation: 0, + padding: MySpacing.xy(8, 0), + splashColor: contentTheme.secondary.withAlpha(36), + child: MyText.bodySmall( + 'Forgot password?', + fontWeight: 600, + color: contentTheme.secondary, + ), ), - MySpacing.width(8), - MyText.bodySmall("Remember Me"), ], ), - ), - MyButton.text( - onPressed: controller.goToForgotPassword, - elevation: 0, - padding: MySpacing.xy(8, 0), - splashColor: contentTheme.secondary.withAlpha(36), - child: MyText.bodySmall('Forgot password?', color: contentTheme.secondary), - ), - ], - ), - MySpacing.height(28), - Center( - child: MyButton.rounded( - onPressed: controller.onLogin, - elevation: 0, - padding: MySpacing.xy(20, 16), - backgroundColor: contentTheme.primary, - child: MyText.labelMedium('Login', color: contentTheme.onPrimary), + MySpacing.height(28), + + /// Login Button + Center( + child: MyButton.rounded( + onPressed: controller.onLogin, + elevation: 2, + padding: MySpacing.xy(24, 16), + borderRadiusAll: 16, + backgroundColor: contentTheme.primary, + child: MyText.labelMedium( + 'Login', + fontWeight: 600, + color: contentTheme.onPrimary, + ), + ), + ), + MySpacing.height(16), + + /// Register Link + Center( + child: MyButton.text( + onPressed: controller.gotoRegister, + elevation: 0, + padding: MySpacing.xy(12, 8), + splashColor: contentTheme.secondary.withAlpha(30), + child: MyText.bodySmall( + "Request a Demo", + color: contentTheme.secondary, + fontWeight: 600, + ), + ), + ), + ], ), ), - Center( - child: MyButton.text( - onPressed: controller.gotoRegister, - elevation: 0, - padding: MySpacing.x(16), - splashColor: contentTheme.secondary.withValues(alpha:0.1), - child: MyText.bodySmall('I haven\'t account'), - ), - ), - ], - ), - ); - },), + ), + ); + }, + ), ); } } 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 8d64b90..0795ff4 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,16 @@ import FlutterMacOS import Foundation +import file_picker +import geolocator_apple import path_provider_foundation import quill_native_bridge_macos import shared_preferences_foundation 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 f074c30..ae4dd33 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -157,10 +157,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "89500471922dd3a89ab0d6e13ab4a2268c25474bff4ca7c628f55c76e0ced1de" + sha256: cacfdc5abe93e64d418caa9256eef663499ad791bb688d9fd12c85a311968fba url: "https://pub.dev" source: hosted - version: "8.1.5" + version: "8.3.2" file_selector_linux: dependency: transitive description: @@ -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: @@ -366,7 +422,7 @@ packages: source: hosted version: "0.15.5" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 @@ -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 8b242a4..12a850f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,9 @@ dependencies: appflowy_board: ^0.1.2 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 )