chnages has been made
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../dto/advances_list_response_dto.dart';
|
||||
|
||||
abstract class AdvanceRemoteDataSource {
|
||||
Future<AdvanceResponseDto> createAdvance(AdvanceRequestDto request);
|
||||
Future<AdvancesListResponseDto> getAdvances();
|
||||
Future<AdvancesListResponseDto> getAdvances(String employeeId);
|
||||
}
|
||||
|
||||
class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource {
|
||||
@@ -71,9 +71,12 @@ class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AdvancesListResponseDto> getAdvances() async {
|
||||
Future<AdvancesListResponseDto> getAdvances(String employeeId) async {
|
||||
try {
|
||||
final response = await apiClient.get('/SalaryInAdvance');
|
||||
final response = await apiClient.get(
|
||||
'/SalaryInAdvance',
|
||||
queryParameters: {'EmployeeId': employeeId},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
|
||||
@@ -9,7 +9,7 @@ import '../dto/vacations_list_response_dto.dart';
|
||||
abstract class VacationRemoteDataSource {
|
||||
Future<VacationResponseDto> createVacation(VacationRequestDto request);
|
||||
Future<VacationTypesResponseDto> getVacationTypes();
|
||||
Future<VacationsListResponseDto> getVacations();
|
||||
Future<VacationsListResponseDto> getVacations(String employeeId);
|
||||
}
|
||||
|
||||
class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
|
||||
@@ -125,9 +125,12 @@ class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<VacationsListResponseDto> getVacations() async {
|
||||
Future<VacationsListResponseDto> getVacations(String employeeId) async {
|
||||
try {
|
||||
final response = await apiClient.get('/Vacation');
|
||||
final response = await apiClient.get(
|
||||
'/Vacation',
|
||||
queryParameters: {'EmployeeId': employeeId},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
|
||||
@@ -31,7 +31,8 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data != null
|
||||
data:
|
||||
responseDto.data != null
|
||||
? AdvanceDataModel(
|
||||
employeeId: responseDto.data!.employeeId,
|
||||
employeeFullName: responseDto.data!.employeeFullName,
|
||||
@@ -61,19 +62,24 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AdvancesListResponseModel>> getAdvances() async {
|
||||
Future<Either<Failure, AdvancesListResponseModel>> getAdvances(
|
||||
String employeeId,
|
||||
) async {
|
||||
try {
|
||||
final responseDto = await remoteDataSource.getAdvances();
|
||||
final responseDto = await remoteDataSource.getAdvances(employeeId);
|
||||
|
||||
// Convert DTO to Model
|
||||
final responseModel = AdvancesListResponseModel(
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data != null
|
||||
data:
|
||||
responseDto.data != null
|
||||
? AdvancesListDataModel(
|
||||
items: responseDto.data!.items
|
||||
.map((dto) => AdvanceDataModel(
|
||||
items:
|
||||
responseDto.data!.items
|
||||
.map(
|
||||
(dto) => AdvanceDataModel(
|
||||
employeeId: dto.employeeId,
|
||||
employeeFullName: dto.employeeFullName,
|
||||
date: dto.date,
|
||||
@@ -87,7 +93,8 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
|
||||
updatedAt: dto.updatedAt,
|
||||
deletedAt: dto.deletedAt,
|
||||
isDeleted: dto.isDeleted,
|
||||
))
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
pageNumber: responseDto.data!.pageNumber,
|
||||
pageSize: responseDto.data!.pageSize,
|
||||
|
||||
@@ -34,7 +34,8 @@ class VacationRepositoryImpl implements VacationRepository {
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data != null
|
||||
data:
|
||||
responseDto.data != null
|
||||
? VacationDataModel(
|
||||
employeeId: responseDto.data!.employeeId,
|
||||
employeeFullName: responseDto.data!.employeeFullName,
|
||||
@@ -74,11 +75,11 @@ class VacationRepositoryImpl implements VacationRepository {
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data
|
||||
.map((dto) => VacationTypeModel(
|
||||
value: dto.value,
|
||||
name: dto.name,
|
||||
))
|
||||
data:
|
||||
responseDto.data
|
||||
.map(
|
||||
(dto) => VacationTypeModel(value: dto.value, name: dto.name),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
@@ -93,19 +94,24 @@ class VacationRepositoryImpl implements VacationRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, VacationsListResponseModel>> getVacations() async {
|
||||
Future<Either<Failure, VacationsListResponseModel>> getVacations(
|
||||
String employeeId,
|
||||
) async {
|
||||
try {
|
||||
final responseDto = await remoteDataSource.getVacations();
|
||||
final responseDto = await remoteDataSource.getVacations(employeeId);
|
||||
|
||||
// Convert DTO to Model
|
||||
final responseModel = VacationsListResponseModel(
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data != null
|
||||
data:
|
||||
responseDto.data != null
|
||||
? VacationsListDataModel(
|
||||
items: responseDto.data!.items
|
||||
.map((dto) => VacationDataModel(
|
||||
items:
|
||||
responseDto.data!.items
|
||||
.map(
|
||||
(dto) => VacationDataModel(
|
||||
employeeId: dto.employeeId,
|
||||
employeeFullName: dto.employeeFullName,
|
||||
startDate: dto.startDate,
|
||||
@@ -120,7 +126,8 @@ class VacationRepositoryImpl implements VacationRepository {
|
||||
updatedAt: dto.updatedAt,
|
||||
deletedAt: dto.deletedAt,
|
||||
isDeleted: dto.isDeleted,
|
||||
))
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
pageNumber: responseDto.data!.pageNumber,
|
||||
pageSize: responseDto.data!.pageSize,
|
||||
|
||||
@@ -7,5 +7,7 @@ abstract class AdvanceRepository {
|
||||
Future<Either<Failure, AdvanceResponseModel>> createAdvance(
|
||||
AdvanceRequestModel request,
|
||||
);
|
||||
Future<Either<Failure, AdvancesListResponseModel>> getAdvances();
|
||||
Future<Either<Failure, AdvancesListResponseModel>> getAdvances(
|
||||
String employeeId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,5 +10,7 @@ abstract class VacationRepository {
|
||||
VacationRequest request,
|
||||
);
|
||||
Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes();
|
||||
Future<Either<Failure, VacationsListResponseModel>> getVacations();
|
||||
Future<Either<Failure, VacationsListResponseModel>> getVacations(
|
||||
String employeeId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class GetAdvancesUseCase {
|
||||
|
||||
GetAdvancesUseCase({required this.repository});
|
||||
|
||||
Future<Either<Failure, AdvancesListResponseModel>> call() {
|
||||
return repository.getAdvances();
|
||||
Future<Either<Failure, AdvancesListResponseModel>> call(String employeeId) {
|
||||
return repository.getAdvances(employeeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class GetVacationsUseCase {
|
||||
|
||||
GetVacationsUseCase({required this.repository});
|
||||
|
||||
Future<Either<Failure, VacationsListResponseModel>> call() {
|
||||
return repository.getVacations();
|
||||
Future<Either<Failure, VacationsListResponseModel>> call(String employeeId) {
|
||||
return repository.getVacations(employeeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'core/di/injection_container.dart';
|
||||
import 'presentation/screens/splash_screen.dart';
|
||||
|
||||
void main() async {
|
||||
try {
|
||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
|
||||
@@ -12,6 +13,28 @@ void main() async {
|
||||
await initializeDependencies();
|
||||
|
||||
runApp(const CodaApp());
|
||||
} catch (e) {
|
||||
debugPrint('CRITICAL INITIALIZATION ERROR: $e');
|
||||
// If initialization fails, show a simple error screen instead of a broken app
|
||||
runApp(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
'Failed to start the app. Please try: \n1. flutter clean\n2. flutter pub get\n\nError: $e',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
// Ensure splash is removed so user can see the error
|
||||
FlutterNativeSplash.remove();
|
||||
}
|
||||
}
|
||||
|
||||
class CodaApp extends StatelessWidget {
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../domain/usecases/get_advances_usecase.dart';
|
||||
import '../../domain/models/vacation_response_model.dart';
|
||||
// import '../../domain/models/advances_list_response_model.dart';
|
||||
import '../../domain/models/advance_request_model.dart';
|
||||
import '../../data/datasources/user_local_data_source.dart';
|
||||
// import '../../core/error/failures.dart';
|
||||
|
||||
class HolidayScreen extends StatefulWidget {
|
||||
@@ -38,6 +39,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
List<AdvanceRequest> _advanceRequests = [];
|
||||
bool _isLoadingVacations = false;
|
||||
bool _isLoadingAdvances = false;
|
||||
String? _employeeId;
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
bool _showActions = true;
|
||||
@@ -74,9 +76,13 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
}
|
||||
|
||||
void _initializeData() async {
|
||||
_employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
// Load from API
|
||||
_loadVacationsFromAPI();
|
||||
_loadAdvancesFromAPI();
|
||||
}
|
||||
|
||||
// Also listen to local changes (for newly created requests)
|
||||
_requestService.leaveRequestsStream.listen((requests) {
|
||||
@@ -94,11 +100,13 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadVacationsFromAPI() async {
|
||||
if (_employeeId == null || _employeeId!.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
_isLoadingVacations = true;
|
||||
});
|
||||
|
||||
final result = await _getVacationsUseCase();
|
||||
final result = await _getVacationsUseCase(_employeeId!);
|
||||
result.fold(
|
||||
(failure) {
|
||||
if (mounted) {
|
||||
@@ -178,11 +186,13 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadAdvancesFromAPI() async {
|
||||
if (_employeeId == null || _employeeId!.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
_isLoadingAdvances = true;
|
||||
});
|
||||
|
||||
final result = await _getAdvancesUseCase();
|
||||
final result = await _getAdvancesUseCase(_employeeId!);
|
||||
result.fold(
|
||||
(failure) {
|
||||
if (mounted) {
|
||||
|
||||
@@ -22,7 +22,7 @@ class RequestLeaveScreen extends StatefulWidget {
|
||||
|
||||
class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
// Dropdown value - initialize with default
|
||||
String leaveType = "إجازة مرضية ";
|
||||
String leaveType = "إجازة مرضية";
|
||||
|
||||
// Toggle switch
|
||||
bool isTimedLeave = false;
|
||||
@@ -41,12 +41,133 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
final RequestService _requestService = RequestService();
|
||||
|
||||
// Use cases
|
||||
final CreateVacationUseCase _createVacationUseCase = sl<CreateVacationUseCase>();
|
||||
final GetVacationTypesUseCase _getVacationTypesUseCase = sl<GetVacationTypesUseCase>();
|
||||
final CreateVacationUseCase _createVacationUseCase =
|
||||
sl<CreateVacationUseCase>();
|
||||
final GetVacationTypesUseCase _getVacationTypesUseCase =
|
||||
sl<GetVacationTypesUseCase>();
|
||||
|
||||
// Vacation types from API
|
||||
List<VacationTypeModel> _vacationTypes = [];
|
||||
int? _selectedVacationTypeValue; // Store selected type value instead of string
|
||||
|
||||
// Store selected type value instead of string
|
||||
int? _selectedVacationTypeValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadVacationTypes();
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// STEP A: single helper for selecting type safely
|
||||
// -------------------------------
|
||||
void _setSelectedVacationType(int value) {
|
||||
_selectedVacationTypeValue = value;
|
||||
|
||||
if (_vacationTypes.isNotEmpty) {
|
||||
final selectedType = _vacationTypes.firstWhere(
|
||||
(t) => t.value == value,
|
||||
orElse: () => _vacationTypes.first,
|
||||
);
|
||||
leaveType = _getArabicName(selectedType.name);
|
||||
} else {
|
||||
// Fallback text (when API not loaded yet)
|
||||
if (value == 0) leaveType = "إجازة زمنية";
|
||||
if (value == 1) leaveType = "إجازة مرضية";
|
||||
if (value == 2) leaveType = "إجازة مدفوعة";
|
||||
if (value == 3) leaveType = "إجازة غير مدفوعة";
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// STEP B: single helper for timed leave rule
|
||||
// -------------------------------
|
||||
void _setTimedLeave(bool on) {
|
||||
setState(() {
|
||||
isTimedLeave = on;
|
||||
|
||||
if (isTimedLeave) {
|
||||
// FORCE TimeOff (value = 1)
|
||||
_setSelectedVacationType(0);
|
||||
|
||||
// Optional: keep dates consistent for timed leave (same day)
|
||||
toDate = fromDate;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadVacationTypes() async {
|
||||
final result = await _getVacationTypesUseCase();
|
||||
result.fold(
|
||||
(failure) {
|
||||
print('Failed to load vacation types: ${_getFailureMessage(failure)}');
|
||||
},
|
||||
(response) {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_vacationTypes = response.data;
|
||||
|
||||
if (_vacationTypes.isEmpty) return;
|
||||
|
||||
// IMPORTANT:
|
||||
// If toggle already ON -> force TimeOff
|
||||
if (isTimedLeave) {
|
||||
final timeOff = _vacationTypes.firstWhere(
|
||||
(t) => t.value == 1,
|
||||
orElse: () => _vacationTypes.first,
|
||||
);
|
||||
_setSelectedVacationType(timeOff.value);
|
||||
} else {
|
||||
// Otherwise default to SickLeave (2) if available
|
||||
final sickLeave = _vacationTypes.firstWhere(
|
||||
(type) => type.value == 2,
|
||||
orElse: () => _vacationTypes.first,
|
||||
);
|
||||
_setSelectedVacationType(sickLeave.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _getArabicName(String apiName) {
|
||||
switch (apiName) {
|
||||
case 'TimeOff':
|
||||
return 'إجازة زمنية';
|
||||
case 'SickLeave':
|
||||
return 'إجازة مرضية';
|
||||
case 'PaidLeave':
|
||||
return 'إجازة مدفوعة';
|
||||
case 'UnpaidLeave':
|
||||
return 'إجازة غير مدفوعة';
|
||||
default:
|
||||
return apiName;
|
||||
}
|
||||
}
|
||||
|
||||
int _getVacationTypeValue() {
|
||||
if (_selectedVacationTypeValue != null) {
|
||||
return _selectedVacationTypeValue!;
|
||||
}
|
||||
|
||||
if (leaveType.contains("مرضية") || leaveType == "SickLeave") {
|
||||
return 1;
|
||||
} else if (leaveType.contains("مدفوعة") && !leaveType.contains("غير")) {
|
||||
return 2;
|
||||
} else if (leaveType.contains("غير مدفوعة") || leaveType == "UnpaidLeave") {
|
||||
return 3;
|
||||
} else if (leaveType.contains("زمنية") || leaveType == "TimeOff") {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
String _getFailureMessage(Failure failure) {
|
||||
if (failure is ServerFailure) return failure.message;
|
||||
if (failure is NetworkFailure) return failure.message;
|
||||
return 'حدث خطأ غير متوقع';
|
||||
}
|
||||
|
||||
/// PICK DATE
|
||||
Future<void> pickDate(bool isFrom) async {
|
||||
@@ -56,15 +177,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
initialDate: initial,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2035),
|
||||
builder: (context, child) {
|
||||
return Theme(data: ThemeData.dark(), child: child!);
|
||||
},
|
||||
builder: (context, child) => Theme(data: ThemeData.dark(), child: child!),
|
||||
);
|
||||
|
||||
if (newDate != null) {
|
||||
setState(() {
|
||||
if (isFrom) {
|
||||
fromDate = newDate;
|
||||
// If timed leave, keep toDate same as fromDate
|
||||
if (isTimedLeave) toDate = newDate;
|
||||
} else {
|
||||
toDate = newDate;
|
||||
}
|
||||
@@ -78,9 +199,7 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
TimeOfDay? newTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: initial,
|
||||
builder: (context, child) {
|
||||
return Theme(data: ThemeData.dark(), child: child!);
|
||||
},
|
||||
builder: (context, child) => Theme(data: ThemeData.dark(), child: child!),
|
||||
);
|
||||
|
||||
if (newTime != null) {
|
||||
@@ -94,85 +213,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadVacationTypes();
|
||||
}
|
||||
|
||||
Future<void> _loadVacationTypes() async {
|
||||
final result = await _getVacationTypesUseCase();
|
||||
result.fold(
|
||||
(failure) {
|
||||
// Silently fail - user can still submit with default types
|
||||
print('Failed to load vacation types: ${_getFailureMessage(failure)}');
|
||||
},
|
||||
(response) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_vacationTypes = response.data;
|
||||
// Set default to SickLeave (value: 2) if available
|
||||
if (_vacationTypes.isNotEmpty) {
|
||||
final sickLeave = _vacationTypes.firstWhere(
|
||||
(type) => type.value == 2,
|
||||
orElse: () => _vacationTypes.first,
|
||||
);
|
||||
_selectedVacationTypeValue = sickLeave.value;
|
||||
leaveType = _getArabicName(sickLeave.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _getArabicName(String apiName) {
|
||||
// Map API names to Arabic display names
|
||||
switch (apiName) {
|
||||
case 'TimeOff':
|
||||
return 'أجازة زمنية';
|
||||
case 'SickLeave':
|
||||
return 'إجازة مرضية';
|
||||
case 'PaidLeave':
|
||||
return 'إجازة مدفوعة';
|
||||
case 'UnpaidLeave':
|
||||
return 'إجازة غير مدفوعة';
|
||||
default:
|
||||
return apiName;
|
||||
}
|
||||
}
|
||||
|
||||
int _getVacationTypeValue() {
|
||||
// Use selected value if available, otherwise fallback to mapping
|
||||
if (_selectedVacationTypeValue != null) {
|
||||
return _selectedVacationTypeValue!;
|
||||
}
|
||||
// Fallback: Map display names to API type values
|
||||
if (leaveType.contains("مرضية") || leaveType == "SickLeave") {
|
||||
return 2; // SickLeave
|
||||
} else if (leaveType.contains("مدفوعة") && !leaveType.contains("غير")) {
|
||||
return 3; // PaidLeave
|
||||
} else if (leaveType.contains("غير مدفوعة") || leaveType == "UnpaidLeave") {
|
||||
return 4; // UnpaidLeave
|
||||
} else if (leaveType.contains("زمنية") || leaveType == "TimeOff") {
|
||||
return 1; // TimeOff
|
||||
}
|
||||
return 1; // Default to TimeOff
|
||||
}
|
||||
|
||||
String _getFailureMessage(Failure failure) {
|
||||
if (failure is ServerFailure) {
|
||||
return failure.message;
|
||||
} else if (failure is NetworkFailure) {
|
||||
return failure.message;
|
||||
}
|
||||
return 'حدث خطأ غير متوقع';
|
||||
}
|
||||
|
||||
// Method to save the leave request
|
||||
Future<void> _saveLeaveRequest() async {
|
||||
if (reasonController.text.isEmpty) {
|
||||
// Show an error message if reason is empty
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@@ -184,7 +226,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get employee ID
|
||||
final employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
|
||||
if (employeeId == null) {
|
||||
if (mounted) {
|
||||
@@ -198,12 +239,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare dates - if timed leave, use same date with time differences
|
||||
DateTime finalStartDate = fromDate!;
|
||||
DateTime finalEndDate = toDate!;
|
||||
|
||||
if (isTimedLeave) {
|
||||
// For timed leave, use the same date but with different times
|
||||
finalStartDate = DateTime(
|
||||
fromDate!.year,
|
||||
fromDate!.month,
|
||||
@@ -219,15 +258,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
toTime!.minute,
|
||||
);
|
||||
} else {
|
||||
// For regular leave, use dates at midnight
|
||||
finalStartDate = DateTime(fromDate!.year, fromDate!.month, fromDate!.day);
|
||||
finalEndDate = DateTime(toDate!.year, toDate!.month, toDate!.day);
|
||||
}
|
||||
|
||||
// Get vacation type value
|
||||
final typeValue = _getVacationTypeValue();
|
||||
|
||||
// Show loading indicator
|
||||
if (mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -237,7 +273,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
}
|
||||
|
||||
try {
|
||||
// Create vacation request
|
||||
final vacationRequest = VacationRequest(
|
||||
employeeId: employeeId,
|
||||
startDate: finalStartDate,
|
||||
@@ -248,8 +283,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
final result = await _createVacationUseCase(vacationRequest);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // Close loading dialog
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
@@ -261,9 +296,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
);
|
||||
},
|
||||
(response) {
|
||||
// Also save locally for UI display
|
||||
final leaveRequest = LeaveRequest(
|
||||
id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
id:
|
||||
response.data?.id ??
|
||||
DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
leaveType: leaveType,
|
||||
isTimedLeave: isTimedLeave,
|
||||
fromDate: fromDate!,
|
||||
@@ -272,11 +308,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
toTime: toTime!,
|
||||
reason: reasonController.text,
|
||||
requestDate: DateTime.now(),
|
||||
status: "waiting", // Default status
|
||||
status: "waiting",
|
||||
);
|
||||
_requestService.addLeaveRequest(leaveRequest);
|
||||
|
||||
// Show success message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم إرسال طلب الأجازة بنجاح'),
|
||||
@@ -284,14 +319,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
),
|
||||
);
|
||||
|
||||
// Navigate back to the previous screen
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // Close loading dialog
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('حدث خطأ غير متوقع: $e'),
|
||||
@@ -305,12 +338,11 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true, // ✅ IMPORTANT
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: AppBackground(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// In your RequestLeaveScreen's build method, replace the SettingsBar with this:
|
||||
SettingsBar(
|
||||
selectedIndex: -1,
|
||||
onTap: (_) {},
|
||||
@@ -322,8 +354,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
],
|
||||
),
|
||||
|
||||
// Title
|
||||
// const SizedBox(height: 30),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
@@ -331,7 +361,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Fixed alignment for "طلب أجازة"
|
||||
const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text(
|
||||
@@ -345,9 +374,7 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
//=============================
|
||||
// DROPDOWN: نوع الإجازة
|
||||
//=============================
|
||||
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: const Text(
|
||||
@@ -362,7 +389,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(height: 5),
|
||||
|
||||
// Modified dropdown with disabled state
|
||||
Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Opacity(
|
||||
@@ -390,11 +416,13 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
],
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: _vacationTypes.isEmpty
|
||||
child:
|
||||
_vacationTypes.isEmpty
|
||||
? const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: CircularProgressIndicator(),
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: DropdownButton<int>(
|
||||
@@ -410,25 +438,35 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
),
|
||||
isExpanded: true,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
if (value == null) return;
|
||||
|
||||
setState(() {
|
||||
_selectedVacationTypeValue = value;
|
||||
final selectedType = _vacationTypes
|
||||
.firstWhere((t) => t.value == value);
|
||||
leaveType = _getArabicName(selectedType.name);
|
||||
// Set toggle based on selected value (TimeOff = 1)
|
||||
isTimedLeave = value == 1;
|
||||
_setSelectedVacationType(value);
|
||||
});
|
||||
|
||||
// Sync toggle with selection
|
||||
if (value == 0 && !isTimedLeave) {
|
||||
_setTimedLeave(true);
|
||||
} else if (value != 0 &&
|
||||
isTimedLeave) {
|
||||
setState(
|
||||
() => isTimedLeave = false,
|
||||
);
|
||||
}
|
||||
},
|
||||
items: _vacationTypes.map((type) {
|
||||
final arabicName = _getArabicName(type.name);
|
||||
items:
|
||||
_vacationTypes.map((type) {
|
||||
final arabicName =
|
||||
_getArabicName(type.name);
|
||||
return DropdownMenuItem<int>(
|
||||
value: type.value,
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
textDirection:
|
||||
TextDirection.rtl,
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
alignment:
|
||||
Alignment
|
||||
.centerRight,
|
||||
child: Text(arabicName),
|
||||
),
|
||||
),
|
||||
@@ -443,25 +481,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(height: 25),
|
||||
|
||||
//=============================
|
||||
// PERFECT CUSTOM TOGGLE (NEW)
|
||||
//=============================
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isTimedLeave = !isTimedLeave;
|
||||
// Set leave type to TimeOff (value: 1) when toggle is ON
|
||||
if (isTimedLeave) {
|
||||
final timeOffType = _vacationTypes
|
||||
.firstWhere((t) => t.value == 1, orElse: () => _vacationTypes.first);
|
||||
_selectedVacationTypeValue = timeOffType.value;
|
||||
leaveType = _getArabicName(timeOffType.name);
|
||||
}
|
||||
});
|
||||
_setTimedLeave(!isTimedLeave);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
// ---------- TOGGLE ----------
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
width: 75,
|
||||
@@ -472,12 +497,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isTimedLeave
|
||||
? const Color(
|
||||
0xFF0A6B4A,
|
||||
) // ON green track
|
||||
: const Color(
|
||||
0xFF9E9E9E,
|
||||
), // OFF grey track
|
||||
? const Color(0xFF0A6B4A)
|
||||
: const Color(0xFF9E9E9E),
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Align(
|
||||
@@ -492,8 +513,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isTimedLeave
|
||||
? const Color(0xFF12BE85) // ON knob
|
||||
: Colors.white, // OFF knob
|
||||
? const Color(0xFF12BE85)
|
||||
: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
@@ -509,7 +530,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(width: 14),
|
||||
|
||||
// ---------- Dot (ALWAYS visible) ----------
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
@@ -521,7 +541,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(width: 6),
|
||||
|
||||
// ---------- Line (ALWAYS visible) ----------
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 2,
|
||||
@@ -531,7 +550,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// ---------- Label ----------
|
||||
const Text(
|
||||
"إجازة زمنية",
|
||||
style: TextStyle(
|
||||
@@ -546,9 +564,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// =============================
|
||||
// DATE PICKER BOX
|
||||
// =============================
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -565,10 +580,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.end, // ALIGN RIGHT
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// TEXT aligned right
|
||||
const Text(
|
||||
"فترة الإجازة",
|
||||
style: TextStyle(
|
||||
@@ -577,24 +590,17 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// 🟢 YOUR CUSTOM SVG ICON
|
||||
SvgPicture.asset(
|
||||
"assets/images/calendar.svg", // <-- replace with your icon filename
|
||||
"assets/images/calendar.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: Color(
|
||||
0xFF007C46,
|
||||
), // Optional: match your green
|
||||
color: const Color(0xFF007C46),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// From date row
|
||||
_dateRow(
|
||||
label: "من",
|
||||
date: fromDate!,
|
||||
@@ -606,7 +612,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// To date row
|
||||
_dateRow(
|
||||
label: "الى",
|
||||
date: toDate!,
|
||||
@@ -621,9 +626,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// =============================
|
||||
// REASON TEXTFIELD (Two Containers)
|
||||
// =============================
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Directionality(
|
||||
@@ -639,20 +641,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
),
|
||||
),
|
||||
|
||||
// OUTER BORDER CONTAINER
|
||||
Container(
|
||||
padding: const EdgeInsets.all(
|
||||
12,
|
||||
), // border thickness space
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Color(0xFF00FFAA), // green border
|
||||
color: const Color(0xFF00FFAA),
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
|
||||
// INNER TEXTFIELD CONTAINER
|
||||
child: Container(
|
||||
height: 70,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -663,19 +660,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
color: const Color(0xFFEAEAEA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
// Added Directionality to fix text direction
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: TextField(
|
||||
controller: reasonController,
|
||||
maxLines: 5,
|
||||
textAlign:
|
||||
TextAlign
|
||||
.right, // Added this to align text to the right
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText:
|
||||
"اكتب السبب", // Added placeholder text
|
||||
hintText: "اكتب السبب",
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
@@ -685,13 +678,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// CONFIRM BUTTON
|
||||
Center(
|
||||
child: OnboardingButton(
|
||||
text: "تأكيد الطلب",
|
||||
backgroundColor: const Color(0xFFD1FEF0),
|
||||
textColor: Colors.black, // Changed to black
|
||||
onPressed: _saveLeaveRequest, // Call the save method
|
||||
textColor: Colors.black,
|
||||
onPressed: _saveLeaveRequest,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -706,9 +698,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// CUSTOM DATE ROW WIDGET
|
||||
// ===============================================================
|
||||
Widget _dateRow({
|
||||
required String label,
|
||||
required DateTime date,
|
||||
@@ -727,9 +716,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// -----------------------
|
||||
// DATE PART (can be disabled)
|
||||
// -----------------------
|
||||
Opacity(
|
||||
opacity: dateDisabled ? 0.45 : 1,
|
||||
child: IgnorePointer(
|
||||
@@ -757,9 +743,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// -----------------------
|
||||
// TIME PART (always active)
|
||||
// -----------------------
|
||||
GestureDetector(
|
||||
onTap: onTimeTap,
|
||||
child: Text(
|
||||
@@ -779,8 +762,11 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: DateTime.weekday is 1..7 (Mon..Sun)
|
||||
String _weekday(int day) {
|
||||
switch (day) {
|
||||
case 7:
|
||||
return "الأحد";
|
||||
case 1:
|
||||
return "الإثنين";
|
||||
case 2:
|
||||
@@ -793,8 +779,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
return "الجمعة";
|
||||
case 6:
|
||||
return "السبت";
|
||||
case 7:
|
||||
return "الأحد";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||
}
|
||||
|
||||
Future<void> _checkTokenAndNavigate() async {
|
||||
try {
|
||||
// Wait for splash screen display
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
@@ -42,6 +43,16 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||
MaterialPageRoute(builder: (_) => const OnboardingScreen()),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error in _checkTokenAndNavigate: $e');
|
||||
if (mounted) {
|
||||
// Fallback to onboarding if anything fails
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const OnboardingScreen()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user