Merge pull request 'Feature_Login' (#1) from Feature_Login into main

Reviewed-on: #1
This commit is contained in:
vaibhav.surve 2025-04-22 12:16:08 +00:00
commit b94b246731
24 changed files with 1009 additions and 615 deletions

144
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -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();
}
}

View 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.");
}
}
}

View 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;
}
}

View File

@ -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 {
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( if (response.statusCode == 200) {
Map<String, dynamic> data) async { isLoggedIn = true;
await Future.delayed(Duration(seconds: 1));
if (data['email'] != dummyUser.email) { // Parse the response to get the JWT and refresh tokens
return {"email": "This email is not registered"}; final responseData = jsonDecode(response.body);
} else if (data['password'] != "1234567") {
return {"password": "Password is incorrect"}; // 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;
} }
} }

View File

@ -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);
}
} }

View 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,
);
}
}

View 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
);
}
}

View 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,
};
}
}

View File

@ -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
View 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';
}

View File

@ -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()),

View File

@ -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});
@ -17,7 +18,7 @@ class LoginScreen extends StatefulWidget {
State<LoginScreen> createState() => _LoginScreenState(); State<LoginScreen> createState() => _LoginScreenState();
} }
class _LoginScreenState extends State<LoginScreen> with UIMixin{ class _LoginScreenState extends State<LoginScreen> with UIMixin {
late LoginController controller; late LoginController controller;
@override @override
@ -29,114 +30,198 @@ 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: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, padding: MySpacing.xy(2, 40),
mainAxisAlignment: MainAxisAlignment.center, child: Container(
mainAxisSize: MainAxisSize.min, width: double.infinity,
children: [ padding: MySpacing.all(24),
MyText.titleLarge("Sign in with email", fontWeight: 600), decoration: BoxDecoration(
MySpacing.height(12), color: theme.colorScheme.primary.withOpacity(0.02),
MyText.bodyMedium("Make a new doc to bring your words, data and terms together. For free", fontWeight: 600, xMuted: true), borderRadius: BorderRadius.circular(8),
MySpacing.height(12), border: Border.all(
TextFormField( color: contentTheme.primary.withOpacity(0.5),
validator: controller.basicValidator.getValidation('email'), ),
controller: controller.basicValidator.getController('email'), ),
keyboardType: TextInputType.emailAddress, child: Column(
style: MyTextStyle.labelMedium(), crossAxisAlignment: CrossAxisAlignment.start,
decoration: InputDecoration( children: [
labelText: "Email Address", /// Logo
labelStyle: MyTextStyle.bodySmall(xMuted: true), Center(
border: OutlineInputBorder(borderSide: BorderSide.none), child: Image.asset(
filled: true, Images.logoDark,
fillColor: contentTheme.secondary.withAlpha(36), height: 120,
prefixIcon: const Icon(LucideIcons.mail, size: 16), fit: BoxFit.contain,
contentPadding: MySpacing.all(14), ),
isDense: true, ),
isCollapsed: true, MySpacing.height(20),
floatingLabelBehavior: FloatingLabelBehavior.never),
), /// Welcome Text
MySpacing.height(20), Center(
TextFormField( child: MyText.bodyLarge("Welcome Back!", fontWeight: 600),
validator: controller.basicValidator.getValidation('password'), ),
controller: controller.basicValidator.getController('password'), MySpacing.height(4),
keyboardType: TextInputType.visiblePassword, Center(
obscureText: !controller.showPassword, child: MyText.bodySmall("Please sign in to continue."),
style: MyTextStyle.labelMedium(), ),
decoration: InputDecoration( MySpacing.height(20),
labelText: "Password",
labelStyle: MyTextStyle.bodySmall(xMuted: true), /// Email Field
border: OutlineInputBorder(borderSide: BorderSide.none), MyText.bodySmall("Email Address", fontWeight: 600),
filled: true, MySpacing.height(8),
fillColor: contentTheme.secondary.withAlpha(36), Material(
prefixIcon: const Icon(LucideIcons.mail, size: 16), elevation: 2,
contentPadding: MySpacing.all(16), shadowColor: contentTheme.secondary.withAlpha(30),
isCollapsed: true, borderRadius: BorderRadius.circular(12),
isDense: true, child: TextFormField(
floatingLabelBehavior: FloatingLabelBehavior.never, validator:
suffixIcon: InkWell( controller.basicValidator.getValidation('username'),
onTap: controller.onChangeShowPassword, controller:
child: Icon(controller.showPassword ? LucideIcons.eye : LucideIcons.eye_off, size: 16), controller.basicValidator.getController('username'),
)), keyboardType: TextInputType.emailAddress,
), style: MyTextStyle.labelMedium(),
Row( decoration: InputDecoration(
mainAxisAlignment: MainAxisAlignment.spaceBetween, hintText: "Enter your email",
children: [ hintStyle: MyTextStyle.bodySmall(xMuted: true),
InkWell( filled: true,
onTap: () => controller.onChangeCheckBox(!controller.isChecked), fillColor: theme.cardColor,
child: Row( border: OutlineInputBorder(
children: [ borderRadius: BorderRadius.circular(2),
Checkbox( borderSide: BorderSide.none,
onChanged: controller.onChangeCheckBox, ),
value: controller.isChecked, prefixIcon: const Icon(LucideIcons.mail, size: 18),
fillColor: WidgetStatePropertyAll(Colors.white), contentPadding: MySpacing.xy(12, 16),
activeColor: theme.colorScheme.primary, ),
checkColor: contentTheme.primary, ),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ),
visualDensity: getCompactDensity, 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"),
], ],
), ),
), MySpacing.height(28),
MyButton.text(
onPressed: controller.goToForgotPassword, /// Login Button
elevation: 0, Center(
padding: MySpacing.xy(8, 0), child: MyButton.rounded(
splashColor: contentTheme.secondary.withAlpha(36), onPressed: controller.onLogin,
child: MyText.bodySmall('Forgot password?', color: contentTheme.secondary), elevation: 2,
), padding: MySpacing.xy(24, 16),
], borderRadiusAll: 16,
), backgroundColor: contentTheme.primary,
MySpacing.height(28), child: MyText.labelMedium(
Center( 'Login',
child: MyButton.rounded( fontWeight: 600,
onPressed: controller.onLogin, color: contentTheme.onPrimary,
elevation: 0, ),
padding: MySpacing.xy(20, 16), ),
backgroundColor: contentTheme.primary, ),
child: MyText.labelMedium('Login', 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'),
),
),
],
),
);
},),
); );
} }
} }

View 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(),
),
),
],
),
);
}
}

View File

@ -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()),
)
],
),
);
}
}

View File

@ -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"))

View File

@ -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:

View File

@ -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:

View File

@ -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"));
} }

View File

@ -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
) )