Compare commits
3 Commits
99902e743c
...
b94b246731
Author | SHA1 | Date | |
---|---|---|---|
b94b246731 | |||
753bfcad8a | |||
23cfebfc37 |
144
.gitignore
vendored
144
.gitignore
vendored
@ -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
|
@ -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
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 970 KiB After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -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));
|
||||
|
@ -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<ChartSampleData>? chartData;
|
||||
TooltipBehavior? columnToolTip;
|
||||
List<JobRecentApplicationModel> recentApplication = [];
|
||||
List<String> dummyTexts = List.generate(12, (index) => MyTextUtils.getDummyText(60));
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
chartData = <ChartSampleData>[
|
||||
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();
|
||||
}
|
||||
}
|
56
lib/controller/dashboard/attendance_screen_controller.dart
Normal file
56
lib/controller/dashboard/attendance_screen_controller.dart
Normal file
@ -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<AttendanceModel> attendances = [];
|
||||
List<ProjectModel> projects = []; // List of projects
|
||||
String? selectedProjectId; // Currently selected project ID
|
||||
List<EmployeeModel> employees = []; // Employees of the selected project
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
fetchProjects(); // Fetch projects when initializing
|
||||
}
|
||||
|
||||
// Fetch projects from API
|
||||
Future<void> fetchProjects() async {
|
||||
var response = await ApiService.getProjects(); // Call the project API
|
||||
|
||||
if (response != null) {
|
||||
projects = response
|
||||
.map<ProjectModel>((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<void> fetchEmployeesByProject(String? projectId) async {
|
||||
if (projectId == null) return;
|
||||
|
||||
var response = await ApiService.getEmployeesByProject(int.parse(projectId));
|
||||
|
||||
if (response != null) {
|
||||
employees = response
|
||||
.map<EmployeeModel>((json) => EmployeeModel.fromJson(json))
|
||||
.toList();
|
||||
update(); // Trigger UI rebuild
|
||||
} else {
|
||||
print("Failed to fetch employees for project $projectId.");
|
||||
}
|
||||
}
|
||||
}
|
77
lib/helpers/services/api_service.dart
Normal file
77
lib/helpers/services/api_service.dart
Normal file
@ -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<List<dynamic>?> 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<List<dynamic>?> 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;
|
||||
}
|
||||
}
|
@ -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<Map<String, String>?> loginUser(Map<String, dynamic> 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<Map<String, String>?> loginUser(
|
||||
Map<String, dynamic> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<bool> removeLoggedInUser() async {
|
||||
return preferences.remove(_loggedInUserKey);
|
||||
}
|
||||
|
||||
// Add methods to handle JWT and Refresh Token
|
||||
static Future<bool> setToken(String key, String token) {
|
||||
return preferences.setString(key, token);
|
||||
}
|
||||
|
||||
static String? getToken(String key) {
|
||||
return preferences.getString(key);
|
||||
}
|
||||
|
||||
static Future<bool> 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<bool> setJwtToken(String jwtToken) {
|
||||
return setToken(_jwtTokenKey, jwtToken);
|
||||
}
|
||||
|
||||
static Future<bool> setRefreshToken(String refreshToken) {
|
||||
return setToken(_refreshTokenKey, refreshToken);
|
||||
}
|
||||
}
|
||||
|
37
lib/model/attendance_model.dart
Normal file
37
lib/model/attendance_model.dart
Normal file
@ -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<String, dynamic> 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,
|
||||
);
|
||||
}
|
||||
}
|
28
lib/model/employee_model.dart
Normal file
28
lib/model/employee_model.dart
Normal file
@ -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<String, dynamic> 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
|
||||
);
|
||||
}
|
||||
}
|
62
lib/model/project_model.dart
Normal file
62
lib/model/project_model.dart
Normal file
@ -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<String, dynamic> 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<String, dynamic> 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,
|
||||
};
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
||||
|
11
lib/res/assets_res.dart
Normal file
11
lib/res/assets_res.dart
Normal file
@ -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';
|
||||
}
|
@ -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()),
|
||||
|
@ -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<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> with UIMixin{
|
||||
class _LoginScreenState extends State<LoginScreen> with UIMixin {
|
||||
late LoginController controller;
|
||||
|
||||
@override
|
||||
@ -29,114 +30,198 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin{
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AuthLayout(
|
||||
child: GetBuilder(
|
||||
child: GetBuilder<LoginController>(
|
||||
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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
200
lib/view/dashboard/attendanceScreen.dart
Normal file
200
lib/view/dashboard/attendanceScreen.dart
Normal file
@ -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<AttendanceScreen> createState() => _AttendanceScreenState();
|
||||
}
|
||||
|
||||
class _AttendanceScreenState extends State<AttendanceScreen> 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<String>(
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
attendanceController.selectedProjectId = value;
|
||||
attendanceController.fetchEmployeesByProject(value);
|
||||
});
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
return attendanceController.projects.map((project) {
|
||||
return PopupMenuItem<String>(
|
||||
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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<AttendanceScreen> createState() => _AttendanceScreenState();
|
||||
}
|
||||
|
||||
class _AttendanceScreenState extends State<AttendanceScreen> 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<ChartSampleData, String>(
|
||||
explode: true,
|
||||
dataSource: <ChartSampleData>[
|
||||
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<ChartSampleData, String>(
|
||||
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<ChartSampleData, String>(
|
||||
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()),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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"))
|
||||
|
126
pubspec.lock
126
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:
|
||||
|
@ -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:
|
||||
|
@ -7,11 +7,17 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
geolocator_windows
|
||||
permission_handler_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user