Feature_Login #1
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
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
|
*.lock
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
*.swp
|
*.swp
|
||||||
@ -16,10 +20,46 @@ migrate_working_dir/
|
|||||||
*.iws
|
*.iws
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# Visual Studio Code related
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
.classpath
|
||||||
# is commented out by default.
|
.project
|
||||||
#.vscode/
|
.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
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
@ -27,17 +67,97 @@ migrate_working_dir/
|
|||||||
.dart_tool/
|
.dart_tool/
|
||||||
.flutter-plugins
|
.flutter-plugins
|
||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
|
**/generated_plugin_registrant.dart
|
||||||
|
.packages
|
||||||
|
.pub-preload-cache/
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.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
|
app.*.symbols
|
||||||
|
|
||||||
# Obfuscation related
|
# Exceptions to above rules.
|
||||||
app.*.map.json
|
!**/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
|
# Monorepo
|
||||||
/android/app/debug
|
.cipd
|
||||||
/android/app/profile
|
.gclient
|
||||||
/android/app/release
|
.gclient_entries
|
||||||
|
.python-version
|
||||||
|
.gclient_previous_custom_vars
|
||||||
|
.gclient_previous_sync_commits
|
@ -18,7 +18,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
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
|
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;
|
bool showPassword = false, isChecked = false;
|
||||||
|
|
||||||
final String _dummyEmail = "demo@example.com";
|
final String _dummyEmail = "admin@marcobms.com";
|
||||||
final String _dummyPassword = "1234567";
|
final String _dummyPassword = "User@123";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
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',
|
basicValidator.addField('password',
|
||||||
required: true, label: "Password", validators: [MyLengthValidator(min: 6, max: 10)], controller: TextEditingController(text: _dummyPassword));
|
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/helpers/services/storage/local_storage.dart';
|
||||||
import 'package:marco/model/user.dart';
|
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
static bool isLoggedIn = false;
|
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 {
|
||||||
static Future<Map<String, String>?> loginUser(
|
final response = await http.post(
|
||||||
Map<String, dynamic> data) async {
|
Uri.parse('https://api.marcoaiot.com/api/auth/login'),
|
||||||
await Future.delayed(Duration(seconds: 1));
|
headers: {'Content-Type': 'application/json'},
|
||||||
if (data['email'] != dummyUser.email) {
|
body: jsonEncode(data),
|
||||||
return {"email": "This email is not registered"};
|
);
|
||||||
} else if (data['password'] != "1234567") {
|
|
||||||
return {"password": "Password is incorrect"};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
isLoggedIn = true;
|
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);
|
await LocalStorage.setLoggedInUser(true);
|
||||||
|
|
||||||
|
// Return null to indicate success
|
||||||
return null;
|
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."};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ class LocalStorage {
|
|||||||
static const String _loggedInUserKey = "user";
|
static const String _loggedInUserKey = "user";
|
||||||
static const String _themeCustomizerKey = "theme_customizer";
|
static const String _themeCustomizerKey = "theme_customizer";
|
||||||
static const String _languageKey = "lang_code";
|
static const String _languageKey = "lang_code";
|
||||||
|
static const String _jwtTokenKey = "jwt_token";
|
||||||
|
static const String _refreshTokenKey = "refresh_token";
|
||||||
|
|
||||||
static SharedPreferences? _preferencesInstance;
|
static SharedPreferences? _preferencesInstance;
|
||||||
|
|
||||||
@ -47,4 +49,34 @@ class LocalStorage {
|
|||||||
static Future<bool> removeLoggedInUser() async {
|
static Future<bool> removeLoggedInUser() async {
|
||||||
return preferences.remove(_loggedInUserKey);
|
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';
|
import 'package:marco/model/identifier_model.dart';
|
||||||
|
|
||||||
class User extends IdentifierModel {
|
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";
|
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/coming_soon_screen.dart';
|
||||||
import 'package:marco/view/error_pages/error_404_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/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 {
|
class AuthMiddleware extends GetMiddleware {
|
||||||
@override
|
@override
|
||||||
RouteSettings? redirect(String? route) {
|
RouteSettings? redirect(String? route) {
|
||||||
@ -23,7 +23,7 @@ getPageRoute() {
|
|||||||
GetPage(name: '/', page: () => const EcommerceScreen(), middlewares: [AuthMiddleware()]),
|
GetPage(name: '/', page: () => const EcommerceScreen(), middlewares: [AuthMiddleware()]),
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
GetPage(name: '/dashboard/attendance', page: () => const AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
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.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text_style.dart';
|
import 'package:marco/helpers/widgets/my_text_style.dart';
|
||||||
import 'package:marco/view/layouts/auth_layout.dart';
|
import 'package:marco/view/layouts/auth_layout.dart';
|
||||||
|
import 'package:marco/images.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
@ -29,76 +30,139 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin{
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AuthLayout(
|
return AuthLayout(
|
||||||
child: GetBuilder(
|
child: GetBuilder<LoginController>(
|
||||||
init: controller,
|
init: controller,
|
||||||
tag: 'login_controller',
|
tag: 'login_controller',
|
||||||
builder: (controller) {
|
builder: (controller) {
|
||||||
return Form(
|
return Form(
|
||||||
key: controller.basicValidator.formKey,
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
MyText.titleLarge("Sign in with email", fontWeight: 600),
|
/// Logo
|
||||||
MySpacing.height(12),
|
Center(
|
||||||
MyText.bodyMedium("Make a new doc to bring your words, data and terms together. For free", fontWeight: 600, xMuted: true),
|
child: Image.asset(
|
||||||
MySpacing.height(12),
|
Images.logoDark,
|
||||||
TextFormField(
|
height: 120,
|
||||||
validator: controller.basicValidator.getValidation('email'),
|
fit: BoxFit.contain,
|
||||||
controller: controller.basicValidator.getController('email'),
|
),
|
||||||
|
),
|
||||||
|
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,
|
keyboardType: TextInputType.emailAddress,
|
||||||
style: MyTextStyle.labelMedium(),
|
style: MyTextStyle.labelMedium(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Email Address",
|
hintText: "Enter your email",
|
||||||
labelStyle: MyTextStyle.bodySmall(xMuted: true),
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
border: OutlineInputBorder(borderSide: BorderSide.none),
|
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: contentTheme.secondary.withAlpha(36),
|
fillColor: theme.cardColor,
|
||||||
prefixIcon: const Icon(LucideIcons.mail, size: 16),
|
border: OutlineInputBorder(
|
||||||
contentPadding: MySpacing.all(14),
|
borderRadius: BorderRadius.circular(2),
|
||||||
isDense: true,
|
borderSide: BorderSide.none,
|
||||||
isCollapsed: true,
|
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never),
|
|
||||||
),
|
),
|
||||||
MySpacing.height(20),
|
prefixIcon: const Icon(LucideIcons.mail, size: 18),
|
||||||
TextFormField(
|
contentPadding: MySpacing.xy(12, 16),
|
||||||
validator: controller.basicValidator.getValidation('password'),
|
),
|
||||||
controller: controller.basicValidator.getController('password'),
|
),
|
||||||
|
),
|
||||||
|
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,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
obscureText: !controller.showPassword,
|
obscureText: !controller.showPassword,
|
||||||
style: MyTextStyle.labelMedium(),
|
style: MyTextStyle.labelMedium(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Password",
|
hintText: "Enter your password",
|
||||||
labelStyle: MyTextStyle.bodySmall(xMuted: true),
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
border: OutlineInputBorder(borderSide: BorderSide.none),
|
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: contentTheme.secondary.withAlpha(36),
|
fillColor: theme.cardColor,
|
||||||
prefixIcon: const Icon(LucideIcons.mail, size: 16),
|
border: OutlineInputBorder(
|
||||||
contentPadding: MySpacing.all(16),
|
borderRadius: BorderRadius.circular(2),
|
||||||
isCollapsed: true,
|
borderSide: BorderSide.none,
|
||||||
isDense: true,
|
),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
prefixIcon: const Icon(LucideIcons.lock, size: 18),
|
||||||
suffixIcon: InkWell(
|
suffixIcon: InkWell(
|
||||||
onTap: controller.onChangeShowPassword,
|
onTap: controller.onChangeShowPassword,
|
||||||
child: Icon(controller.showPassword ? LucideIcons.eye : LucideIcons.eye_off, size: 16),
|
child: Icon(
|
||||||
)),
|
controller.showPassword
|
||||||
|
? LucideIcons.eye
|
||||||
|
: LucideIcons.eye_off,
|
||||||
|
size: 18,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
contentPadding: MySpacing.all(3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
MySpacing.height(16),
|
||||||
|
|
||||||
|
/// Remember Me + Forgot Password
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => controller.onChangeCheckBox(!controller.isChecked),
|
onTap: () => controller
|
||||||
|
.onChangeCheckBox(!controller.isChecked),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
onChanged: controller.onChangeCheckBox,
|
onChanged: controller.onChangeCheckBox,
|
||||||
value: controller.isChecked,
|
value: controller.isChecked,
|
||||||
fillColor: WidgetStatePropertyAll(Colors.white),
|
shape: RoundedRectangleBorder(
|
||||||
activeColor: theme.colorScheme.primary,
|
borderRadius: BorderRadius.circular(4),
|
||||||
checkColor: contentTheme.primary,
|
),
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
fillColor: WidgetStatePropertyAll(
|
||||||
|
contentTheme.secondary),
|
||||||
|
checkColor: contentTheme.onPrimary,
|
||||||
visualDensity: getCompactDensity,
|
visualDensity: getCompactDensity,
|
||||||
|
materialTapTargetSize:
|
||||||
|
MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
MySpacing.width(8),
|
MySpacing.width(8),
|
||||||
MyText.bodySmall("Remember Me"),
|
MyText.bodySmall("Remember Me"),
|
||||||
@ -110,33 +174,54 @@ class _LoginScreenState extends State<LoginScreen> with UIMixin{
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
padding: MySpacing.xy(8, 0),
|
padding: MySpacing.xy(8, 0),
|
||||||
splashColor: contentTheme.secondary.withAlpha(36),
|
splashColor: contentTheme.secondary.withAlpha(36),
|
||||||
child: MyText.bodySmall('Forgot password?', color: contentTheme.secondary),
|
child: MyText.bodySmall(
|
||||||
|
'Forgot password?',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: contentTheme.secondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
MySpacing.height(28),
|
MySpacing.height(28),
|
||||||
|
|
||||||
|
/// Login Button
|
||||||
Center(
|
Center(
|
||||||
child: MyButton.rounded(
|
child: MyButton.rounded(
|
||||||
onPressed: controller.onLogin,
|
onPressed: controller.onLogin,
|
||||||
elevation: 0,
|
elevation: 2,
|
||||||
padding: MySpacing.xy(20, 16),
|
padding: MySpacing.xy(24, 16),
|
||||||
|
borderRadiusAll: 16,
|
||||||
backgroundColor: contentTheme.primary,
|
backgroundColor: contentTheme.primary,
|
||||||
child: MyText.labelMedium('Login', color: contentTheme.onPrimary),
|
child: MyText.labelMedium(
|
||||||
|
'Login',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: contentTheme.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(16),
|
||||||
|
|
||||||
|
/// Register Link
|
||||||
Center(
|
Center(
|
||||||
child: MyButton.text(
|
child: MyButton.text(
|
||||||
onPressed: controller.gotoRegister,
|
onPressed: controller.gotoRegister,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
padding: MySpacing.x(16),
|
padding: MySpacing.xy(12, 8),
|
||||||
splashColor: contentTheme.secondary.withValues(alpha:0.1),
|
splashColor: contentTheme.secondary.withAlpha(30),
|
||||||
child: MyText.bodySmall('I haven\'t account'),
|
child: MyText.bodySmall(
|
||||||
|
"Request a Demo",
|
||||||
|
color: contentTheme.secondary,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import file_picker
|
||||||
|
import geolocator_apple
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import quill_native_bridge_macos
|
import quill_native_bridge_macos
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
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"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin"))
|
QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
126
pubspec.lock
126
pubspec.lock
@ -157,10 +157,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "89500471922dd3a89ab0d6e13ab4a2268c25474bff4ca7c628f55c76e0ced1de"
|
sha256: cacfdc5abe93e64d418caa9256eef663499ad791bb688d9fd12c85a311968fba
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.5"
|
version: "8.3.2"
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -185,6 +185,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+3"
|
version: "0.9.3+3"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -293,6 +301,54 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
get:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -366,7 +422,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.5"
|
version: "0.15.5"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
@ -525,6 +581,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
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:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -706,6 +810,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -898,6 +1010,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.3.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -58,6 +58,9 @@ dependencies:
|
|||||||
appflowy_board: ^0.1.2
|
appflowy_board: ^0.1.2
|
||||||
syncfusion_flutter_calendar: ^28.2.6
|
syncfusion_flutter_calendar: ^28.2.6
|
||||||
syncfusion_flutter_maps: ^28.1.33
|
syncfusion_flutter_maps: ^28.1.33
|
||||||
|
http: ^1.2.2
|
||||||
|
geolocator: ^9.0.1
|
||||||
|
permission_handler: ^11.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -7,11 +7,17 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <file_selector_windows/file_selector_windows.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>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
GeolocatorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||||
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
|
geolocator_windows
|
||||||
|
permission_handler_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user