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

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

View File

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

View File

@@ -31,23 +31,24 @@ class AdvanceRepositoryImpl implements AdvanceRepository {
statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess,
message: responseDto.message,
data: responseDto.data != null
? AdvanceDataModel(
employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName,
date: responseDto.data!.date,
amount: responseDto.data!.amount,
submittedBy: responseDto.data!.submittedBy,
submittedByUser: responseDto.data!.submittedByUser,
reason: responseDto.data!.reason,
state: responseDto.data!.state,
id: responseDto.data!.id,
createdAt: responseDto.data!.createdAt,
updatedAt: responseDto.data!.updatedAt,
deletedAt: responseDto.data!.deletedAt,
isDeleted: responseDto.data!.isDeleted,
)
: null,
data:
responseDto.data != null
? AdvanceDataModel(
employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName,
date: responseDto.data!.date,
amount: responseDto.data!.amount,
submittedBy: responseDto.data!.submittedBy,
submittedByUser: responseDto.data!.submittedByUser,
reason: responseDto.data!.reason,
state: responseDto.data!.state,
id: responseDto.data!.id,
createdAt: responseDto.data!.createdAt,
updatedAt: responseDto.data!.updatedAt,
deletedAt: responseDto.data!.deletedAt,
isDeleted: responseDto.data!.isDeleted,
)
: null,
);
return Right(responseModel);
@@ -61,40 +62,46 @@ 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
? AdvancesListDataModel(
items: responseDto.data!.items
.map((dto) => AdvanceDataModel(
employeeId: dto.employeeId,
employeeFullName: dto.employeeFullName,
date: dto.date,
amount: dto.amount,
submittedBy: dto.submittedBy,
submittedByUser: dto.submittedByUser,
reason: dto.reason,
state: dto.state,
id: dto.id,
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
deletedAt: dto.deletedAt,
isDeleted: dto.isDeleted,
))
.toList(),
pageNumber: responseDto.data!.pageNumber,
pageSize: responseDto.data!.pageSize,
totalCount: responseDto.data!.totalCount,
totalPages: responseDto.data!.totalPages,
)
: null,
data:
responseDto.data != null
? AdvancesListDataModel(
items:
responseDto.data!.items
.map(
(dto) => AdvanceDataModel(
employeeId: dto.employeeId,
employeeFullName: dto.employeeFullName,
date: dto.date,
amount: dto.amount,
submittedBy: dto.submittedBy,
submittedByUser: dto.submittedByUser,
reason: dto.reason,
state: dto.state,
id: dto.id,
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
deletedAt: dto.deletedAt,
isDeleted: dto.isDeleted,
),
)
.toList(),
pageNumber: responseDto.data!.pageNumber,
pageSize: responseDto.data!.pageSize,
totalCount: responseDto.data!.totalCount,
totalPages: responseDto.data!.totalPages,
)
: null,
);
return Right(responseModel);

View File

@@ -34,24 +34,25 @@ class VacationRepositoryImpl implements VacationRepository {
statusCode: responseDto.statusCode,
isSuccess: responseDto.isSuccess,
message: responseDto.message,
data: responseDto.data != null
? VacationDataModel(
employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName,
startDate: responseDto.data!.startDate,
endDate: responseDto.data!.endDate,
reason: responseDto.data!.reason,
submittedBy: responseDto.data!.submittedBy,
submittedByUser: responseDto.data!.submittedByUser,
state: responseDto.data!.state,
type: responseDto.data!.type,
id: responseDto.data!.id,
createdAt: responseDto.data!.createdAt,
updatedAt: responseDto.data!.updatedAt,
deletedAt: responseDto.data!.deletedAt,
isDeleted: responseDto.data!.isDeleted,
)
: null,
data:
responseDto.data != null
? VacationDataModel(
employeeId: responseDto.data!.employeeId,
employeeFullName: responseDto.data!.employeeFullName,
startDate: responseDto.data!.startDate,
endDate: responseDto.data!.endDate,
reason: responseDto.data!.reason,
submittedBy: responseDto.data!.submittedBy,
submittedByUser: responseDto.data!.submittedByUser,
state: responseDto.data!.state,
type: responseDto.data!.type,
id: responseDto.data!.id,
createdAt: responseDto.data!.createdAt,
updatedAt: responseDto.data!.updatedAt,
deletedAt: responseDto.data!.deletedAt,
isDeleted: responseDto.data!.isDeleted,
)
: null,
);
return Right(responseModel);
@@ -74,12 +75,12 @@ 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,
))
.toList(),
data:
responseDto.data
.map(
(dto) => VacationTypeModel(value: dto.value, name: dto.name),
)
.toList(),
);
return Right(responseModel);
@@ -93,41 +94,47 @@ 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
? VacationsListDataModel(
items: responseDto.data!.items
.map((dto) => VacationDataModel(
employeeId: dto.employeeId,
employeeFullName: dto.employeeFullName,
startDate: dto.startDate,
endDate: dto.endDate,
reason: dto.reason,
submittedBy: dto.submittedBy,
submittedByUser: dto.submittedByUser,
state: dto.state,
type: dto.type,
id: dto.id,
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
deletedAt: dto.deletedAt,
isDeleted: dto.isDeleted,
))
.toList(),
pageNumber: responseDto.data!.pageNumber,
pageSize: responseDto.data!.pageSize,
totalCount: responseDto.data!.totalCount,
totalPages: responseDto.data!.totalPages,
)
: null,
data:
responseDto.data != null
? VacationsListDataModel(
items:
responseDto.data!.items
.map(
(dto) => VacationDataModel(
employeeId: dto.employeeId,
employeeFullName: dto.employeeFullName,
startDate: dto.startDate,
endDate: dto.endDate,
reason: dto.reason,
submittedBy: dto.submittedBy,
submittedByUser: dto.submittedByUser,
state: dto.state,
type: dto.type,
id: dto.id,
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
deletedAt: dto.deletedAt,
isDeleted: dto.isDeleted,
),
)
.toList(),
pageNumber: responseDto.data!.pageNumber,
pageSize: responseDto.data!.pageSize,
totalCount: responseDto.data!.totalCount,
totalPages: responseDto.data!.totalPages,
)
: null,
);
return Right(responseModel);

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,13 +5,36 @@ import 'core/di/injection_container.dart';
import 'presentation/screens/splash_screen.dart';
void main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
try {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
// Initialize dependency injection
await initializeDependencies();
// Initialize dependency injection
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 {

View File

@@ -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 {
// Load from API
_loadVacationsFromAPI();
_loadAdvancesFromAPI();
_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) {

View File

@@ -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,50 +283,48 @@ 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) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_getFailureMessage(failure)),
backgroundColor: Colors.red,
),
);
},
(response) {
// Also save locally for UI display
final leaveRequest = LeaveRequest(
id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType,
isTimedLeave: isTimedLeave,
fromDate: fromDate!,
toDate: toDate!,
fromTime: fromTime!,
toTime: toTime!,
reason: reasonController.text,
requestDate: DateTime.now(),
status: "waiting", // Default status
);
_requestService.addLeaveRequest(leaveRequest);
result.fold(
(failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_getFailureMessage(failure)),
backgroundColor: Colors.red,
),
);
},
(response) {
final leaveRequest = LeaveRequest(
id:
response.data?.id ??
DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType,
isTimedLeave: isTimedLeave,
fromDate: fromDate!,
toDate: toDate!,
fromTime: fromTime!,
toTime: toTime!,
reason: reasonController.text,
requestDate: DateTime.now(),
status: "waiting",
);
_requestService.addLeaveRequest(leaveRequest);
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم إرسال طلب الأجازة بنجاح'),
backgroundColor: Colors.green,
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم إرسال طلب الأجازة بنجاح'),
backgroundColor: Colors.green,
),
);
// Navigate back to the previous screen
Navigator.pop(context);
},
);
}
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(
@@ -343,11 +372,9 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
),
),
),
const SizedBox(height: 10),
//=============================
// DROPDOWN: نوع الإجازة
//=============================
Align(
alignment: Alignment.centerRight,
child: const Text(
@@ -359,10 +386,9 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
),
),
),
const SizedBox(height: 5),
// Modified dropdown with disabled state
Directionality(
textDirection: TextDirection.rtl,
child: Opacity(
@@ -390,78 +416,77 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
],
),
child: DropdownButtonHideUnderline(
child: _vacationTypes.isEmpty
? const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
)
: DropdownButton<int>(
value: _selectedVacationTypeValue,
icon: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.black,
size: 28,
),
style: const TextStyle(
color: Colors.black,
fontSize: 17,
),
isExpanded: true,
onChanged: (value) {
if (value != null) {
child:
_vacationTypes.isEmpty
? const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child:
CircularProgressIndicator(),
),
)
: DropdownButton<int>(
value: _selectedVacationTypeValue,
icon: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.black,
size: 28,
),
style: const TextStyle(
color: Colors.black,
fontSize: 17,
),
isExpanded: true,
onChanged: (value) {
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);
});
}
},
items: _vacationTypes.map((type) {
final arabicName = _getArabicName(type.name);
return DropdownMenuItem<int>(
value: type.value,
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text(arabicName),
),
),
);
}).toList(),
),
// 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);
return DropdownMenuItem<int>(
value: type.value,
child: Directionality(
textDirection:
TextDirection.rtl,
child: Align(
alignment:
Alignment
.centerRight,
child: Text(arabicName),
),
),
);
}).toList(),
),
),
),
),
),
),
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(
@@ -506,10 +527,9 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
),
),
),
const SizedBox(width: 14),
// ---------- Dot (ALWAYS visible) ----------
Container(
width: 10,
height: 10,
@@ -518,20 +538,18 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
// ---------- Line (ALWAYS visible) ----------
Expanded(
child: Container(
height: 2,
color: const Color(0xFF32C59A),
),
),
const SizedBox(width: 12),
// ---------- Label ----------
const Text(
"إجازة زمنية",
style: TextStyle(
@@ -543,12 +561,9 @@ 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!,
@@ -603,10 +609,9 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
isTimedLeave ? null : () => pickDate(true),
onTimeTap: () => pickTime(true),
),
const SizedBox(height: 15),
// To date row
_dateRow(
label: "الى",
date: toDate!,
@@ -618,12 +623,9 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
],
),
),
const SizedBox(height: 10),
// =============================
// REASON TEXTFIELD (Two Containers)
// =============================
Align(
alignment: Alignment.centerRight,
child: Directionality(
@@ -638,21 +640,16 @@ 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,35 +660,30 @@ 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),
),
),
),
),
),
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 "";
}

View File

@@ -21,26 +21,37 @@ class _SplashScreenState extends State<SplashScreen> {
}
Future<void> _checkTokenAndNavigate() async {
// Wait for splash screen display
await Future.delayed(const Duration(seconds: 2));
try {
// Wait for splash screen display
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
if (!mounted) return;
// Check if token exists in cache
final token = await sl<UserLocalDataSource>().getCachedUserToken();
if (token != null && token.isNotEmpty) {
// Token exists, navigate directly to MainPage
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
);
} else {
// No token, navigate to OnboardingScreen
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const OnboardingScreen()),
);
// Check if token exists in cache
final token = await sl<UserLocalDataSource>().getCachedUserToken();
if (token != null && token.isNotEmpty) {
// Token exists, navigate directly to MainPage
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
);
} else {
// No token, navigate to OnboardingScreen
Navigator.pushReplacement(
context,
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()),
);
}
}
}