chnages has been made

This commit is contained in:
Daniah Ayad Al-sultani
2026-02-12 14:25:19 +03:00
parent a7930d19e5
commit 08b16df68d
13 changed files with 467 additions and 413 deletions

View File

@@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <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-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />

View File

@@ -7,7 +7,7 @@ import '../dto/advances_list_response_dto.dart';
abstract class AdvanceRemoteDataSource { abstract class AdvanceRemoteDataSource {
Future<AdvanceResponseDto> createAdvance(AdvanceRequestDto request); Future<AdvanceResponseDto> createAdvance(AdvanceRequestDto request);
Future<AdvancesListResponseDto> getAdvances(); Future<AdvancesListResponseDto> getAdvances(String employeeId);
} }
class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource { class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource {
@@ -71,9 +71,12 @@ class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource {
} }
@override @override
Future<AdvancesListResponseDto> getAdvances() async { Future<AdvancesListResponseDto> getAdvances(String employeeId) async {
try { try {
final response = await apiClient.get('/SalaryInAdvance'); final response = await apiClient.get(
'/SalaryInAdvance',
queryParameters: {'EmployeeId': employeeId},
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final responseData = response.data; final responseData = response.data;

View File

@@ -9,7 +9,7 @@ import '../dto/vacations_list_response_dto.dart';
abstract class VacationRemoteDataSource { abstract class VacationRemoteDataSource {
Future<VacationResponseDto> createVacation(VacationRequestDto request); Future<VacationResponseDto> createVacation(VacationRequestDto request);
Future<VacationTypesResponseDto> getVacationTypes(); Future<VacationTypesResponseDto> getVacationTypes();
Future<VacationsListResponseDto> getVacations(); Future<VacationsListResponseDto> getVacations(String employeeId);
} }
class VacationRemoteDataSourceImpl implements VacationRemoteDataSource { class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
@@ -125,9 +125,12 @@ class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
} }
@override @override
Future<VacationsListResponseDto> getVacations() async { Future<VacationsListResponseDto> getVacations(String employeeId) async {
try { try {
final response = await apiClient.get('/Vacation'); final response = await apiClient.get(
'/Vacation',
queryParameters: {'EmployeeId': employeeId},
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final responseData = response.data; final responseData = response.data;

View File

@@ -31,7 +31,8 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
statusCode: responseDto.statusCode, statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess, isSuccess: responseDto.isSuccess,
message: responseDto.message, message: responseDto.message,
data: responseDto.data != null data:
responseDto.data != null
? AdvanceDataModel( ? AdvanceDataModel(
employeeId: responseDto.data!.employeeId, employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName, employeeFullName: responseDto.data!.employeeFullName,
@@ -61,19 +62,24 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
} }
@override @override
Future<Either<Failure, AdvancesListResponseModel>> getAdvances() async { Future<Either<Failure, AdvancesListResponseModel>> getAdvances(
String employeeId,
) async {
try { try {
final responseDto = await remoteDataSource.getAdvances(); final responseDto = await remoteDataSource.getAdvances(employeeId);
// Convert DTO to Model // Convert DTO to Model
final responseModel = AdvancesListResponseModel( final responseModel = AdvancesListResponseModel(
statusCode: responseDto.statusCode, statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess, isSuccess: responseDto.isSuccess,
message: responseDto.message, message: responseDto.message,
data: responseDto.data != null data:
responseDto.data != null
? AdvancesListDataModel( ? AdvancesListDataModel(
items: responseDto.data!.items items:
.map((dto) => AdvanceDataModel( responseDto.data!.items
.map(
(dto) => AdvanceDataModel(
employeeId: dto.employeeId, employeeId: dto.employeeId,
employeeFullName: dto.employeeFullName, employeeFullName: dto.employeeFullName,
date: dto.date, date: dto.date,
@@ -87,7 +93,8 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
updatedAt: dto.updatedAt, updatedAt: dto.updatedAt,
deletedAt: dto.deletedAt, deletedAt: dto.deletedAt,
isDeleted: dto.isDeleted, isDeleted: dto.isDeleted,
)) ),
)
.toList(), .toList(),
pageNumber: responseDto.data!.pageNumber, pageNumber: responseDto.data!.pageNumber,
pageSize: responseDto.data!.pageSize, pageSize: responseDto.data!.pageSize,

View File

@@ -34,7 +34,8 @@ class VacationRepositoryImpl implements VacationRepository {
statusCode: responseDto.statusCode, statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess, isSuccess: responseDto.isSuccess,
message: responseDto.message, message: responseDto.message,
data: responseDto.data != null data:
responseDto.data != null
? VacationDataModel( ? VacationDataModel(
employeeId: responseDto.data!.employeeId, employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName, employeeFullName: responseDto.data!.employeeFullName,
@@ -74,11 +75,11 @@ class VacationRepositoryImpl implements VacationRepository {
statusCode: responseDto.statusCode, statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess, isSuccess: responseDto.isSuccess,
message: responseDto.message, message: responseDto.message,
data: responseDto.data data:
.map((dto) => VacationTypeModel( responseDto.data
value: dto.value, .map(
name: dto.name, (dto) => VacationTypeModel(value: dto.value, name: dto.name),
)) )
.toList(), .toList(),
); );
@@ -93,19 +94,24 @@ class VacationRepositoryImpl implements VacationRepository {
} }
@override @override
Future<Either<Failure, VacationsListResponseModel>> getVacations() async { Future<Either<Failure, VacationsListResponseModel>> getVacations(
String employeeId,
) async {
try { try {
final responseDto = await remoteDataSource.getVacations(); final responseDto = await remoteDataSource.getVacations(employeeId);
// Convert DTO to Model // Convert DTO to Model
final responseModel = VacationsListResponseModel( final responseModel = VacationsListResponseModel(
statusCode: responseDto.statusCode, statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess, isSuccess: responseDto.isSuccess,
message: responseDto.message, message: responseDto.message,
data: responseDto.data != null data:
responseDto.data != null
? VacationsListDataModel( ? VacationsListDataModel(
items: responseDto.data!.items items:
.map((dto) => VacationDataModel( responseDto.data!.items
.map(
(dto) => VacationDataModel(
employeeId: dto.employeeId, employeeId: dto.employeeId,
employeeFullName: dto.employeeFullName, employeeFullName: dto.employeeFullName,
startDate: dto.startDate, startDate: dto.startDate,
@@ -120,7 +126,8 @@ class VacationRepositoryImpl implements VacationRepository {
updatedAt: dto.updatedAt, updatedAt: dto.updatedAt,
deletedAt: dto.deletedAt, deletedAt: dto.deletedAt,
isDeleted: dto.isDeleted, isDeleted: dto.isDeleted,
)) ),
)
.toList(), .toList(),
pageNumber: responseDto.data!.pageNumber, pageNumber: responseDto.data!.pageNumber,
pageSize: responseDto.data!.pageSize, pageSize: responseDto.data!.pageSize,

View File

@@ -7,5 +7,7 @@ abstract class AdvanceRepository {
Future<Either<Failure, AdvanceResponseModel>> createAdvance( Future<Either<Failure, AdvanceResponseModel>> createAdvance(
AdvanceRequestModel request, AdvanceRequestModel request,
); );
Future<Either<Failure, AdvancesListResponseModel>> getAdvances(); Future<Either<Failure, AdvancesListResponseModel>> getAdvances(
String employeeId,
);
} }

View File

@@ -10,5 +10,7 @@ abstract class VacationRepository {
VacationRequest request, VacationRequest request,
); );
Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes(); Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes();
Future<Either<Failure, VacationsListResponseModel>> getVacations(); Future<Either<Failure, VacationsListResponseModel>> getVacations(
String employeeId,
);
} }

View File

@@ -8,7 +8,7 @@ class GetAdvancesUseCase {
GetAdvancesUseCase({required this.repository}); GetAdvancesUseCase({required this.repository});
Future<Either<Failure, AdvancesListResponseModel>> call() { Future<Either<Failure, AdvancesListResponseModel>> call(String employeeId) {
return repository.getAdvances(); return repository.getAdvances(employeeId);
} }
} }

View File

@@ -8,7 +8,7 @@ class GetVacationsUseCase {
GetVacationsUseCase({required this.repository}); GetVacationsUseCase({required this.repository});
Future<Either<Failure, VacationsListResponseModel>> call() { Future<Either<Failure, VacationsListResponseModel>> call(String employeeId) {
return repository.getVacations(); return repository.getVacations(employeeId);
} }
} }

View File

@@ -5,6 +5,7 @@ import 'core/di/injection_container.dart';
import 'presentation/screens/splash_screen.dart'; import 'presentation/screens/splash_screen.dart';
void main() async { void main() async {
try {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
@@ -12,6 +13,28 @@ void main() async {
await initializeDependencies(); await initializeDependencies();
runApp(const CodaApp()); 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 { class CodaApp extends StatelessWidget {

View File

@@ -17,6 +17,7 @@ import '../../domain/usecases/get_advances_usecase.dart';
import '../../domain/models/vacation_response_model.dart'; import '../../domain/models/vacation_response_model.dart';
// import '../../domain/models/advances_list_response_model.dart'; // import '../../domain/models/advances_list_response_model.dart';
import '../../domain/models/advance_request_model.dart'; import '../../domain/models/advance_request_model.dart';
import '../../data/datasources/user_local_data_source.dart';
// import '../../core/error/failures.dart'; // import '../../core/error/failures.dart';
class HolidayScreen extends StatefulWidget { class HolidayScreen extends StatefulWidget {
@@ -38,6 +39,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
List<AdvanceRequest> _advanceRequests = []; List<AdvanceRequest> _advanceRequests = [];
bool _isLoadingVacations = false; bool _isLoadingVacations = false;
bool _isLoadingAdvances = false; bool _isLoadingAdvances = false;
String? _employeeId;
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
bool _showActions = true; bool _showActions = true;
@@ -74,9 +76,13 @@ class _HolidayScreenState extends State<HolidayScreen> {
} }
void _initializeData() async { void _initializeData() async {
_employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
if (mounted) {
setState(() {});
// Load from API // Load from API
_loadVacationsFromAPI(); _loadVacationsFromAPI();
_loadAdvancesFromAPI(); _loadAdvancesFromAPI();
}
// Also listen to local changes (for newly created requests) // Also listen to local changes (for newly created requests)
_requestService.leaveRequestsStream.listen((requests) { _requestService.leaveRequestsStream.listen((requests) {
@@ -94,11 +100,13 @@ class _HolidayScreenState extends State<HolidayScreen> {
} }
Future<void> _loadVacationsFromAPI() async { Future<void> _loadVacationsFromAPI() async {
if (_employeeId == null || _employeeId!.isEmpty) return;
setState(() { setState(() {
_isLoadingVacations = true; _isLoadingVacations = true;
}); });
final result = await _getVacationsUseCase(); final result = await _getVacationsUseCase(_employeeId!);
result.fold( result.fold(
(failure) { (failure) {
if (mounted) { if (mounted) {
@@ -178,11 +186,13 @@ class _HolidayScreenState extends State<HolidayScreen> {
} }
Future<void> _loadAdvancesFromAPI() async { Future<void> _loadAdvancesFromAPI() async {
if (_employeeId == null || _employeeId!.isEmpty) return;
setState(() { setState(() {
_isLoadingAdvances = true; _isLoadingAdvances = true;
}); });
final result = await _getAdvancesUseCase(); final result = await _getAdvancesUseCase(_employeeId!);
result.fold( result.fold(
(failure) { (failure) {
if (mounted) { if (mounted) {

View File

@@ -41,12 +41,133 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
final RequestService _requestService = RequestService(); final RequestService _requestService = RequestService();
// Use cases // Use cases
final CreateVacationUseCase _createVacationUseCase = sl<CreateVacationUseCase>(); final CreateVacationUseCase _createVacationUseCase =
final GetVacationTypesUseCase _getVacationTypesUseCase = sl<GetVacationTypesUseCase>(); sl<CreateVacationUseCase>();
final GetVacationTypesUseCase _getVacationTypesUseCase =
sl<GetVacationTypesUseCase>();
// Vacation types from API // Vacation types from API
List<VacationTypeModel> _vacationTypes = []; 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 /// PICK DATE
Future<void> pickDate(bool isFrom) async { Future<void> pickDate(bool isFrom) async {
@@ -56,15 +177,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
initialDate: initial, initialDate: initial,
firstDate: DateTime(2020), firstDate: DateTime(2020),
lastDate: DateTime(2035), lastDate: DateTime(2035),
builder: (context, child) { builder: (context, child) => Theme(data: ThemeData.dark(), child: child!),
return Theme(data: ThemeData.dark(), child: child!);
},
); );
if (newDate != null) { if (newDate != null) {
setState(() { setState(() {
if (isFrom) { if (isFrom) {
fromDate = newDate; fromDate = newDate;
// If timed leave, keep toDate same as fromDate
if (isTimedLeave) toDate = newDate;
} else { } else {
toDate = newDate; toDate = newDate;
} }
@@ -78,9 +199,7 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
TimeOfDay? newTime = await showTimePicker( TimeOfDay? newTime = await showTimePicker(
context: context, context: context,
initialTime: initial, initialTime: initial,
builder: (context, child) { builder: (context, child) => Theme(data: ThemeData.dark(), child: child!),
return Theme(data: ThemeData.dark(), child: child!);
},
); );
if (newTime != null) { 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 { Future<void> _saveLeaveRequest() async {
if (reasonController.text.isEmpty) { if (reasonController.text.isEmpty) {
// Show an error message if reason is empty
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
@@ -184,7 +226,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
return; return;
} }
// Get employee ID
final employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId(); final employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
if (employeeId == null) { if (employeeId == null) {
if (mounted) { if (mounted) {
@@ -198,12 +239,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
return; return;
} }
// Prepare dates - if timed leave, use same date with time differences
DateTime finalStartDate = fromDate!; DateTime finalStartDate = fromDate!;
DateTime finalEndDate = toDate!; DateTime finalEndDate = toDate!;
if (isTimedLeave) { if (isTimedLeave) {
// For timed leave, use the same date but with different times
finalStartDate = DateTime( finalStartDate = DateTime(
fromDate!.year, fromDate!.year,
fromDate!.month, fromDate!.month,
@@ -219,15 +258,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
toTime!.minute, toTime!.minute,
); );
} else { } else {
// For regular leave, use dates at midnight
finalStartDate = DateTime(fromDate!.year, fromDate!.month, fromDate!.day); finalStartDate = DateTime(fromDate!.year, fromDate!.month, fromDate!.day);
finalEndDate = DateTime(toDate!.year, toDate!.month, toDate!.day); finalEndDate = DateTime(toDate!.year, toDate!.month, toDate!.day);
} }
// Get vacation type value
final typeValue = _getVacationTypeValue(); final typeValue = _getVacationTypeValue();
// Show loading indicator
if (mounted) { if (mounted) {
showDialog( showDialog(
context: context, context: context,
@@ -237,7 +273,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
} }
try { try {
// Create vacation request
final vacationRequest = VacationRequest( final vacationRequest = VacationRequest(
employeeId: employeeId, employeeId: employeeId,
startDate: finalStartDate, startDate: finalStartDate,
@@ -248,8 +283,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
final result = await _createVacationUseCase(vacationRequest); final result = await _createVacationUseCase(vacationRequest);
if (mounted) { if (!mounted) return;
Navigator.pop(context); // Close loading dialog Navigator.pop(context);
result.fold( result.fold(
(failure) { (failure) {
@@ -261,9 +296,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
); );
}, },
(response) { (response) {
// Also save locally for UI display
final leaveRequest = LeaveRequest( final leaveRequest = LeaveRequest(
id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(), id:
response.data?.id ??
DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType, leaveType: leaveType,
isTimedLeave: isTimedLeave, isTimedLeave: isTimedLeave,
fromDate: fromDate!, fromDate: fromDate!,
@@ -272,11 +308,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
toTime: toTime!, toTime: toTime!,
reason: reasonController.text, reason: reasonController.text,
requestDate: DateTime.now(), requestDate: DateTime.now(),
status: "waiting", // Default status status: "waiting",
); );
_requestService.addLeaveRequest(leaveRequest); _requestService.addLeaveRequest(leaveRequest);
// Show success message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('تم إرسال طلب الأجازة بنجاح'), content: Text('تم إرسال طلب الأجازة بنجاح'),
@@ -284,14 +319,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
), ),
); );
// Navigate back to the previous screen
Navigator.pop(context); Navigator.pop(context);
}, },
); );
}
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
Navigator.pop(context); // Close loading dialog Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('حدث خطأ غير متوقع: $e'), content: Text('حدث خطأ غير متوقع: $e'),
@@ -305,12 +338,11 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, // ✅ IMPORTANT resizeToAvoidBottomInset: true,
body: AppBackground( body: AppBackground(
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
// In your RequestLeaveScreen's build method, replace the SettingsBar with this:
SettingsBar( SettingsBar(
selectedIndex: -1, selectedIndex: -1,
onTap: (_) {}, onTap: (_) {},
@@ -322,8 +354,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
], ],
), ),
// Title
// const SizedBox(height: 30),
Flexible( Flexible(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
@@ -331,7 +361,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Fixed alignment for "طلب أجازة"
const Align( const Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: Text( child: Text(
@@ -345,9 +374,7 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
//=============================
// DROPDOWN: نوع الإجازة
//=============================
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: const Text( child: const Text(
@@ -362,7 +389,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(height: 5), const SizedBox(height: 5),
// Modified dropdown with disabled state
Directionality( Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: Opacity( child: Opacity(
@@ -390,11 +416,13 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
], ],
), ),
child: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: _vacationTypes.isEmpty child:
_vacationTypes.isEmpty
? const Center( ? const Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(), child:
CircularProgressIndicator(),
), ),
) )
: DropdownButton<int>( : DropdownButton<int>(
@@ -410,25 +438,35 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
), ),
isExpanded: true, isExpanded: true,
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value == null) return;
setState(() { setState(() {
_selectedVacationTypeValue = value; _setSelectedVacationType(value);
final selectedType = _vacationTypes
.firstWhere((t) => t.value == value);
leaveType = _getArabicName(selectedType.name);
// Set toggle based on selected value (TimeOff = 1)
isTimedLeave = value == 1;
}); });
// Sync toggle with selection
if (value == 0 && !isTimedLeave) {
_setTimedLeave(true);
} else if (value != 0 &&
isTimedLeave) {
setState(
() => isTimedLeave = false,
);
} }
}, },
items: _vacationTypes.map((type) { items:
final arabicName = _getArabicName(type.name); _vacationTypes.map((type) {
final arabicName =
_getArabicName(type.name);
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: type.value, value: type.value,
child: Directionality( child: Directionality(
textDirection: TextDirection.rtl, textDirection:
TextDirection.rtl,
child: Align( child: Align(
alignment: Alignment.centerRight, alignment:
Alignment
.centerRight,
child: Text(arabicName), child: Text(arabicName),
), ),
), ),
@@ -443,25 +481,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(height: 25), const SizedBox(height: 25),
//=============================
// PERFECT CUSTOM TOGGLE (NEW)
//=============================
GestureDetector( GestureDetector(
onTap: () { onTap: () {
setState(() { _setTimedLeave(!isTimedLeave);
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);
}
});
}, },
child: Row( child: Row(
children: [ children: [
// ---------- TOGGLE ----------
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
width: 75, width: 75,
@@ -472,12 +497,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: color:
isTimedLeave isTimedLeave
? const Color( ? const Color(0xFF0A6B4A)
0xFF0A6B4A, : const Color(0xFF9E9E9E),
) // ON green track
: const Color(
0xFF9E9E9E,
), // OFF grey track
borderRadius: BorderRadius.circular(40), borderRadius: BorderRadius.circular(40),
), ),
child: Align( child: Align(
@@ -492,8 +513,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: color:
isTimedLeave isTimedLeave
? const Color(0xFF12BE85) // ON knob ? const Color(0xFF12BE85)
: Colors.white, // OFF knob : Colors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -509,7 +530,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(width: 14), const SizedBox(width: 14),
// ---------- Dot (ALWAYS visible) ----------
Container( Container(
width: 10, width: 10,
height: 10, height: 10,
@@ -521,7 +541,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(width: 6), const SizedBox(width: 6),
// ---------- Line (ALWAYS visible) ----------
Expanded( Expanded(
child: Container( child: Container(
height: 2, height: 2,
@@ -531,7 +550,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(width: 12), const SizedBox(width: 12),
// ---------- Label ----------
const Text( const Text(
"إجازة زمنية", "إجازة زمنية",
style: TextStyle( style: TextStyle(
@@ -546,9 +564,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(height: 20), const SizedBox(height: 20),
// =============================
// DATE PICKER BOX
// =============================
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -565,10 +580,8 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
child: Column( child: Column(
children: [ children: [
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.end,
MainAxisAlignment.end, // ALIGN RIGHT
children: [ children: [
// TEXT aligned right
const Text( const Text(
"فترة الإجازة", "فترة الإجازة",
style: TextStyle( style: TextStyle(
@@ -577,24 +590,17 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
// 🟢 YOUR CUSTOM SVG ICON
SvgPicture.asset( SvgPicture.asset(
"assets/images/calendar.svg", // <-- replace with your icon filename "assets/images/calendar.svg",
width: 24, width: 24,
height: 24, height: 24,
color: Color( color: const Color(0xFF007C46),
0xFF007C46,
), // Optional: match your green
), ),
], ],
), ),
const SizedBox(height: 15), const SizedBox(height: 15),
// From date row
_dateRow( _dateRow(
label: "من", label: "من",
date: fromDate!, date: fromDate!,
@@ -606,7 +612,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(height: 15), const SizedBox(height: 15),
// To date row
_dateRow( _dateRow(
label: "الى", label: "الى",
date: toDate!, date: toDate!,
@@ -621,9 +626,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(height: 10), const SizedBox(height: 10),
// =============================
// REASON TEXTFIELD (Two Containers)
// =============================
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Directionality( child: Directionality(
@@ -639,20 +641,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
), ),
), ),
// OUTER BORDER CONTAINER
Container( Container(
padding: const EdgeInsets.all( padding: const EdgeInsets.all(12),
12,
), // border thickness space
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: Color(0xFF00FFAA), // green border color: const Color(0xFF00FFAA),
width: 0.5, width: 0.5,
), ),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
), ),
// INNER TEXTFIELD CONTAINER
child: Container( child: Container(
height: 70, height: 70,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -663,19 +660,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
color: const Color(0xFFEAEAEA), color: const Color(0xFFEAEAEA),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
// Added Directionality to fix text direction
child: Directionality( child: Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: TextField( child: TextField(
controller: reasonController, controller: reasonController,
maxLines: 5, maxLines: 5,
textAlign: textAlign: TextAlign.right,
TextAlign
.right, // Added this to align text to the right
decoration: const InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, border: InputBorder.none,
hintText: hintText: "اكتب السبب",
"اكتب السبب", // Added placeholder text
hintStyle: TextStyle(color: Colors.grey), hintStyle: TextStyle(color: Colors.grey),
), ),
), ),
@@ -685,13 +678,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const SizedBox(height: 20), const SizedBox(height: 20),
// CONFIRM BUTTON
Center( Center(
child: OnboardingButton( child: OnboardingButton(
text: "تأكيد الطلب", text: "تأكيد الطلب",
backgroundColor: const Color(0xFFD1FEF0), backgroundColor: const Color(0xFFD1FEF0),
textColor: Colors.black, // Changed to black textColor: Colors.black,
onPressed: _saveLeaveRequest, // Call the save method onPressed: _saveLeaveRequest,
), ),
), ),
], ],
@@ -706,9 +698,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
); );
} }
// ===============================================================
// CUSTOM DATE ROW WIDGET
// ===============================================================
Widget _dateRow({ Widget _dateRow({
required String label, required String label,
required DateTime date, required DateTime date,
@@ -727,9 +716,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
), ),
child: Row( child: Row(
children: [ children: [
// -----------------------
// DATE PART (can be disabled)
// -----------------------
Opacity( Opacity(
opacity: dateDisabled ? 0.45 : 1, opacity: dateDisabled ? 0.45 : 1,
child: IgnorePointer( child: IgnorePointer(
@@ -757,9 +743,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
const Spacer(), const Spacer(),
// -----------------------
// TIME PART (always active)
// -----------------------
GestureDetector( GestureDetector(
onTap: onTimeTap, onTap: onTimeTap,
child: Text( child: Text(
@@ -779,8 +762,11 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
); );
} }
// NOTE: DateTime.weekday is 1..7 (Mon..Sun)
String _weekday(int day) { String _weekday(int day) {
switch (day) { switch (day) {
case 7:
return "الأحد";
case 1: case 1:
return "الإثنين"; return "الإثنين";
case 2: case 2:
@@ -793,8 +779,6 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
return "الجمعة"; return "الجمعة";
case 6: case 6:
return "السبت"; return "السبت";
case 7:
return "الأحد";
default: default:
return ""; return "";
} }

View File

@@ -21,6 +21,7 @@ class _SplashScreenState extends State<SplashScreen> {
} }
Future<void> _checkTokenAndNavigate() async { Future<void> _checkTokenAndNavigate() async {
try {
// Wait for splash screen display // Wait for splash screen display
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
@@ -42,6 +43,16 @@ class _SplashScreenState extends State<SplashScreen> {
MaterialPageRoute(builder: (_) => const OnboardingScreen()), 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 @override