Compare commits
37 Commits
greeting_c
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
5ad012d602 | 6 days ago |
|
|
3391cf49ef | 1 week ago |
|
|
057e71e3c0 | 1 week ago |
|
|
bdaccf133c | 1 week ago |
|
|
16e2cc604f | 1 week ago |
|
|
d5c3ce4c62 | 1 week ago |
|
|
5da81968d7 | 1 week ago |
|
|
6a80e21c3f | 2 weeks ago |
|
|
b240f55aff | 2 weeks ago |
|
|
3343e4fe10 | 2 weeks ago |
|
|
2c9db30d6a | 2 weeks ago |
|
|
eb3eb1d102 | 2 weeks ago |
|
|
257b803cce | 3 weeks ago |
|
|
dae746587f | 3 weeks ago |
|
|
facb3bf4e6 | 3 weeks ago |
|
|
71d03ba664 | 3 weeks ago |
|
|
9f663df3cc | 4 weeks ago |
|
|
a8f390d49f | 4 weeks ago |
|
|
2c7d3bf963 | 4 weeks ago |
|
|
31d1804f7a | 1 month ago |
|
|
82c9aa8a2f | 1 month ago |
|
|
048c9b395f | 1 month ago |
|
|
eea35e4d77 | 1 month ago |
|
|
19ccee6a20 | 2 months ago |
|
|
5705619ce4 | 2 months ago |
|
|
fe583c234b | 2 months ago |
|
|
a426bc332e | 2 months ago |
|
|
12d8fd95ba | 2 months ago |
|
|
eebaafcf7c | 2 months ago |
|
|
a28eb4426b | 2 months ago |
|
|
dfa7d9a713 | 2 months ago |
|
|
9112b26987 | 2 months ago |
|
|
74bcf3e1b7 | 3 months ago |
|
|
912f7b23f1 | 3 months ago |
|
|
99414bf787 | 3 months ago |
|
|
89ea554c7a | 4 months ago |
|
|
3d91a1444e | 4 months ago |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 730 KiB |
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19.5" height="19.5" viewBox="0 0 19.5 19.5">
|
||||
<g id="graduation-cap" transform="translate(0)">
|
||||
<path id="cap-top" d="M9.75,4.5L3.5,7.5L9.75,10.5L16,7.5L9.75,4.5Z" fill="#2e303a"/>
|
||||
<path id="cap-body" d="M4.5,8.5V13.5C4.5,14.5,6.5,16.5,9.75,16.5C13,16.5,15,14.5,15,13.5V8.5L9.75,11.5L4.5,8.5Z" fill="#2e303a"/>
|
||||
<path id="tassel" d="M16,8V12.5L16.5,13L17,12.5V8H16Z" fill="#2e303a"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 457 B |
|
After Width: | Height: | Size: 1.2 MiB |
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32.774" height="32.788" viewBox="0 0 32.774 32.788">
|
||||
<g id="park-tickets-couple" transform="translate(-0.205 0.001)">
|
||||
<path id="Path_4724" data-name="Path 4724" d="M68.36,485.746a1.657,1.657,0,0,1-1.581,1.3.539.539,0,0,0-.206,1.025,1.656,1.656,0,0,1,.955,1.814l-.14.7a1.744,1.744,0,0,1-2.093,1.278c-.25-.06-.506-.106-.759-.156-.373-.073-.745-.15-1.118-.223-.493-.1-.985-.2-1.477-.3l-1.76-.353c-.655-.133-1.311-.263-1.967-.4-.7-.14-1.4-.279-2.1-.423-.719-.143-1.438-.289-2.153-.433l-2.133-.429-2.036-.409c-.622-.123-1.241-.25-1.863-.373-.539-.107-1.078-.216-1.614-.323l-1.291-.26-.888-.18c-.136-.027-.273-.057-.409-.083-.007,0-.013,0-.02,0a1.686,1.686,0,0,1-.12,1.062,1.705,1.705,0,0,1-1.87.958.5.5,0,0,0-.562.246l-.3.649a.5.5,0,0,0,.176.589c.033.027,24.018,11.084,24.058,11.094a.5.5,0,0,0,.562-.246l.3-.649a.5.5,0,0,0-.176-.589,1.7,1.7,0,0,1,1.388-3,.5.5,0,0,0,.562-.246l.3-.649a.5.5,0,0,0-.176-.589,1.7,1.7,0,0,1,1.388-3,.5.5,0,0,0,.562-.246l.3-.649a.5.5,0,0,0-.176-.589,1.7,1.7,0,0,1,1.388-3,.5.5,0,0,0,.562-.246l.329-.719a.41.41,0,0,0-.2-.546L68.416,485.5Z" transform="translate(-39.26 -469.349)" fill="#2bb8a6"/>
|
||||
<path id="Path_4725" data-name="Path 4725" d="M.519,16.168c.04.02,25.932,5.214,25.975,5.214a.5.5,0,0,0,.489-.369l.14-.7a.5.5,0,0,0-.309-.532,1.7,1.7,0,0,1,.652-3.244.5.5,0,0,0,.489-.369l.1-.512.037-.19a.5.5,0,0,0-.309-.532,1.7,1.7,0,0,1,.652-3.244.5.5,0,0,0,.489-.369l.14-.7a.5.5,0,0,0-.309-.532,1.7,1.7,0,0,1,.652-3.244.5.5,0,0,0,.489-.369l.156-.775a.41.41,0,0,0-.323-.482S3.714,0,3.687,0a.412.412,0,0,0-.4.329L3.128,1.1a.5.5,0,0,0,.309.532A1.7,1.7,0,0,1,2.786,4.88.5.5,0,0,0,2.3,5.25l-.14.7a.5.5,0,0,0,.309.532,1.7,1.7,0,0,1-.652,3.244.5.5,0,0,0-.489.369l-.14.7a.5.5,0,0,0,.309.532A1.7,1.7,0,0,1,.842,14.57a.5.5,0,0,0-.489.369l-.14.7A.494.494,0,0,0,.519,16.168ZM22.7,7.639a.831.831,0,1,1,1.631.326l-1.1,5.464L22.555,16.8a.832.832,0,0,1-.815.669.9.9,0,0,1-.163-.017.833.833,0,0,1-.652-.978l.755-3.757ZM7.7,4.628a.831.831,0,0,1,1.631.326L8.948,6.84,8.519,8.98,8.173,10.7l-.616,3.081a.832.832,0,0,1-.815.669.9.9,0,0,1-.163-.017.4.4,0,0,1-.067-.017.829.829,0,0,1-.582-.962l.123-.622.323-1.611Z" fill="#125765"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="23.959" height="37.006" viewBox="0 0 23.959 37.006">
|
||||
<g id="Layer_x0020_1" transform="translate(-300.998)">
|
||||
<g id="_411123120" transform="translate(301)">
|
||||
<g id="Group_8657" data-name="Group 8657" transform="translate(3.252)">
|
||||
<path id="Path_13674" data-name="Path 13674" d="M612.691,6.612H619.8V1.691h-7.111ZM620.669,8.3h-8.823A.833.833,0,0,1,611,7.458V.845A.847.847,0,0,1,611.846,0h8.823a.861.861,0,0,1,.845.845V7.458A.847.847,0,0,1,620.669,8.3Z" transform="translate(-607.531)" fill="#2bb8a6"/>
|
||||
<path id="Path_13675" data-name="Path 13675" d="M466.845,305h15.089a.833.833,0,0,1,.845.845,5.44,5.44,0,0,1-5.42,5.441h-5.918A5.478,5.478,0,0,1,466,305.845.847.847,0,0,1,466.845,305Z" transform="translate(-465.675 -298.388)" fill="#2bb8a6"/>
|
||||
<path id="Path_13676" data-name="Path 13676" d="M527.845,0A.861.861,0,0,0,527,.845a.847.847,0,0,0,.846.845h12.444a.851.851,0,0,0,.867-.845A.865.865,0,0,0,540.289,0Z" transform="translate(-525.352)" fill="#2bb8a6"/>
|
||||
<g id="Group_8656" data-name="Group 8656" transform="translate(0 33.863)">
|
||||
<path id="Path_13677" data-name="Path 13677" d="M455.553,1562v.867a2.276,2.276,0,1,1-4.553,0V1562h4.553Z" transform="translate(-451 -1562)" fill="#2bb8a6"/>
|
||||
<path id="Path_13678" data-name="Path 13678" d="M1050.553,1562v.867a2.276,2.276,0,0,1-4.553,0V1562h4.553Z" transform="translate(-1033.101 -1562)" fill="#2bb8a6"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="Subtraction_59" data-name="Subtraction 59" d="M20570.063,22681.4h-16.975a.817.817,0,0,1-.238-.023,3.388,3.388,0,0,1-2.236-1.018,3.476,3.476,0,0,1-1.02-2.471v-19.4a3.557,3.557,0,0,1,1.02-2.475,3.457,3.457,0,0,1,1.906-.975,1.745,1.745,0,0,0-.193.8,6.288,6.288,0,0,0,6.289,6.285h5.92a6.288,6.288,0,0,0,6.285-6.285,1.692,1.692,0,0,0-.217-.8,3.472,3.472,0,0,1,2.949,3.449v19.4a3.476,3.476,0,0,1-1.02,2.471,3.383,3.383,0,0,1-2.236,1.018A.879.879,0,0,1,20570.063,22681.4Zm-10.756-7.187a2.114,2.114,0,1,0,1.494.619A2.1,2.1,0,0,0,20559.307,22674.211Zm4.027.732-.613.621.961.939-.961.943.613.621.963-.949v0l.973.949.609-.621-.961-.943.961-.939-.609-.621-.973.951Zm-4.025-6.031a2.112,2.112,0,0,0-.807,4.063,2.071,2.071,0,0,0,.809.164,2.113,2.113,0,0,0,0-4.227Zm3.543,1.406-.645.584,1.531,1.7,2.637-2.6-.609-.619-1.992,1.963Zm-3.539-6.709a2.158,2.158,0,0,0-.486.059,2.111,2.111,0,1,0,.969,4.109,2.123,2.123,0,0,0,1.57-2.545A2.1,2.1,0,0,0,20559.313,22663.609Zm3.539,1.266-.645.58,1.531,1.7,2.637-2.6-.609-.619-1.992,1.963Z" transform="translate(-20549.596 -22648.381)" fill="#125765"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg id="Outline" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ffffff;}</style></defs><path style="fill: #FFF" class="cls-1" d="M376,170a11.34,11.34,0,0,0-16,.15L215.28,317.64l-64-65.22a11.34,11.34,0,0,0-16.18,15.88l72.16,73.58a11.35,11.35,0,0,0,16,.15,12,12,0,0,0,1.43-1.7l.4-.37,151-154A11.33,11.33,0,0,0,376,170Z"/><path style="fill: #FFF" class="cls-1" d="M256,58.65c-108.38,0-196.56,88.18-196.56,196.56S147.62,451.77,256,451.77,452.56,363.6,452.56,255.21,364.38,58.65,256,58.65Zm0,370.45c-95.88,0-173.88-78-173.88-173.89S160.12,81.33,256,81.33s173.88,78,173.88,173.88S351.88,429.1,256,429.1Z"/></svg>
|
||||
|
After Width: | Height: | Size: 665 B |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg data-name="Livello 1" id="Livello_1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><title/><path style="fill: #fff;" d="M64,0a64,64,0,1,0,64,64A64.07,64.07,0,0,0,64,0Zm0,122a58,58,0,1,1,58-58A58.07,58.07,0,0,1,64,122Z"/><path style="fill: #fff;" d="M92.12,35.79a3,3,0,0,0-4.24,0L64,59.75l-23.87-24A3,3,0,0,0,35.88,40L59.76,64,35.88,88a3,3,0,0,0,4.25,4.24L64,68.25l23.88,24A3,3,0,0,0,92.13,88L68.24,64,92.13,40A3,3,0,0,0,92.12,35.79Z"/></svg>
|
||||
|
After Width: | Height: | Size: 479 B |
@ -0,0 +1,140 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mohem_flutter_app/api/api_client.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/classes/consts.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart';
|
||||
import 'package:mohem_flutter_app/models/generic_response_model.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_department_sections.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_project_departments.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_projects.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_section_topics.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_details.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_transactions.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_ticket_types.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_tickets_list.dart';
|
||||
import 'package:mohem_flutter_app/models/mowadhafhi/get_transaction_attachment_model.dart';
|
||||
|
||||
class EtqanApiClient {
|
||||
static final EtqanApiClient _instance = EtqanApiClient._internal();
|
||||
|
||||
EtqanApiClient._internal();
|
||||
|
||||
factory EtqanApiClient() => _instance;
|
||||
|
||||
Future<List<EtqanGetProjectsResponse>> getEtqanProjects() async {
|
||||
String url = "${ApiConsts.cocRest}ETQAN_GetProjects";
|
||||
Map<String, dynamic> postParams = <String, dynamic>{"P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject(
|
||||
(json) {
|
||||
List<EtqanGetProjectsResponse>? responseData = <EtqanGetProjectsResponse>[];
|
||||
if (json["ETQAN_GetProjects_Response"] != null && json["ETQAN_GetProjects_Response"].isNotEmpty) {
|
||||
json["ETQAN_GetProjects_Response"].forEach((element) {
|
||||
responseData.add(EtqanGetProjectsResponse.fromJson(element));
|
||||
});
|
||||
}
|
||||
return responseData;
|
||||
},
|
||||
url,
|
||||
postParams,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<EtqanGetEmployeeOvrRequestsResponse>> getEmployeeEtqanRequests() async {
|
||||
String url = "${ApiConsts.cocRest}ETQAN_GetEmployeeOVRRequests";
|
||||
Map<String, dynamic> postParams = <String, dynamic>{
|
||||
"ETQAN_EmployeeID": AppState().memberInformationList?.eMPLOYEENUMBER,
|
||||
"P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER,
|
||||
};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject(
|
||||
(json) {
|
||||
List<EtqanGetEmployeeOvrRequestsResponse> responseData = <EtqanGetEmployeeOvrRequestsResponse>[];
|
||||
if (json["ETQAN_GetEmployeeOVRRequests_Response"] != null && json["ETQAN_GetEmployeeOVRRequests_Response"].isNotEmpty) {
|
||||
json["ETQAN_GetEmployeeOVRRequests_Response"].forEach((element) {
|
||||
responseData.add(EtqanGetEmployeeOvrRequestsResponse.fromJson(element));
|
||||
});
|
||||
}
|
||||
|
||||
if (responseData.isNotEmpty) {
|
||||
responseData.sort((EtqanGetEmployeeOvrRequestsResponse a, EtqanGetEmployeeOvrRequestsResponse b) {
|
||||
if (a.createdDate == null && b.createdDate == null) return 0;
|
||||
if (a.createdDate == null) return 1;
|
||||
if (b.createdDate == null) return -1;
|
||||
|
||||
try {
|
||||
// Parse dates in format "26-Feb-2026 12:54 PM" or "01-Mar-2026 02:45 PM"
|
||||
DateFormat dateFormat = DateFormat("dd-MMM-yyyy hh:mm a", "en_US");
|
||||
DateTime dateA = dateFormat.parse(a.createdDate!);
|
||||
DateTime dateB = dateFormat.parse(b.createdDate!);
|
||||
return dateB.compareTo(dateA); // Descending order (newest first)
|
||||
} catch (e) {
|
||||
return (b.createdDate ?? '').compareTo(a.createdDate ?? '');
|
||||
}
|
||||
});
|
||||
}
|
||||
return responseData;
|
||||
},
|
||||
url,
|
||||
postParams,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<EtqanGetIncidentRequestResponse>> getEtqanIncidentRequests(String ticketNo) async {
|
||||
String url = "${ApiConsts.cocRest}ETQAN_GetIncidentRequest";
|
||||
Map<String, dynamic> postParams = <String, dynamic>{"ETQAN_TicketNumber": ticketNo, "P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER};
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject(
|
||||
(json) {
|
||||
List<EtqanGetIncidentRequestResponse> responseData = <EtqanGetIncidentRequestResponse>[];
|
||||
if (json["ETQAN_GetIncidentRequest_Response"] != null && json["ETQAN_GetIncidentRequest_Response"].isNotEmpty) {
|
||||
json["ETQAN_GetIncidentRequest_Response"].forEach((element) {
|
||||
responseData.add(EtqanGetIncidentRequestResponse.fromJson(element));
|
||||
});
|
||||
}
|
||||
return responseData;
|
||||
},
|
||||
url,
|
||||
postParams,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> createRequest(String description, int? projectID, String? attachmentBase64, bool? isAnonymous) async {
|
||||
String url = "${ApiConsts.cocRest}ETQAN_CreateIncident";
|
||||
Map<String, dynamic> postParams = <String, dynamic>{
|
||||
"P_SELECTED_EMPLOYEE_NUMBER": AppState().memberInformationList?.eMPLOYEENUMBER,
|
||||
"P_USER_NAME": AppState().memberInformationList?.eMPLOYEENUMBER,
|
||||
"ETQAN_Emp_EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER,
|
||||
"ETQAN_Description": description,
|
||||
"ETQAN_ProjectId": projectID,
|
||||
"ETQAN_FileInfoBase64": attachmentBase64,
|
||||
"ETQAN_IsAnonymous": isAnonymous ?? false,
|
||||
};
|
||||
|
||||
postParams.addAll(AppState().postParamsJson);
|
||||
return await ApiClient().postJsonForObject(
|
||||
(json) {
|
||||
if (json["ETQAN_CreateIncident_Response"] != null) {
|
||||
var response = json["ETQAN_CreateIncident_Response"];
|
||||
if (response["id"] != null && response["ticketNumber"] != null) {
|
||||
return {"id": response["id"], "ticketNumber": response["ticketNumber"]};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
// "ETQAN_CreateIncident_Response": {
|
||||
// "id": "00adabe4-4a32-4b35-1f00-08de4d152e7c",
|
||||
// "ticketNumber": "OVR/OLY/2026/224"
|
||||
// },
|
||||
},
|
||||
url,
|
||||
postParams,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
class CoursesResponseModel {
|
||||
bool? success;
|
||||
int? status;
|
||||
String? username;
|
||||
List<CourseStatus>? coursesStatus;
|
||||
int? totalCourses;
|
||||
|
||||
CoursesResponseModel({
|
||||
this.success,
|
||||
this.status,
|
||||
this.username,
|
||||
this.coursesStatus,
|
||||
this.totalCourses,
|
||||
});
|
||||
|
||||
CoursesResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
success = json['success'];
|
||||
status = json['status'];
|
||||
username = json['username'];
|
||||
if (json['courses_status'] != null) {
|
||||
coursesStatus = <CourseStatus>[];
|
||||
json['courses_status'].forEach((v) {
|
||||
coursesStatus!.add(CourseStatus.fromJson(v));
|
||||
});
|
||||
}
|
||||
totalCourses = json['total_courses'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['success'] = success;
|
||||
data['status'] = status;
|
||||
data['username'] = username;
|
||||
if (coursesStatus != null) {
|
||||
data['courses_status'] = coursesStatus!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
data['total_courses'] = totalCourses;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class CourseStatus {
|
||||
String? cId;
|
||||
String? cFullname;
|
||||
String? cNameAr;
|
||||
String? cShortname;
|
||||
String? cStatus;
|
||||
String? cIsMohemmAffective;
|
||||
String? cStartdate;
|
||||
String? cEnddate;
|
||||
String? cUrl;
|
||||
String? token;
|
||||
CourseStatus({
|
||||
this.cId,
|
||||
this.cFullname,
|
||||
this.cNameAr,
|
||||
this.cShortname,
|
||||
this.cStatus,
|
||||
this.cIsMohemmAffective,
|
||||
this.cStartdate,
|
||||
this.cEnddate,
|
||||
this.cUrl,
|
||||
this.token
|
||||
});
|
||||
|
||||
CourseStatus.fromJson(Map<String, dynamic> json) {
|
||||
cId = json['c_id'];
|
||||
cFullname = json['c_fullname'];
|
||||
cNameAr = json['c_name_ar'];
|
||||
cShortname = json['c_shortname'];
|
||||
cStatus = json['c_status'];
|
||||
cIsMohemmAffective = json['c_is_mohemm_affective'];
|
||||
cStartdate = json['c_startdate'];
|
||||
cEnddate = json['c_enddate'];
|
||||
cUrl = json['c_url'];
|
||||
token =json['c_token'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['c_id'] = cId;
|
||||
data['c_fullname'] = cFullname;
|
||||
data['c_name_ar'] = cNameAr;
|
||||
data['c_shortname'] = cShortname;
|
||||
data['c_status'] = cStatus;
|
||||
data['c_is_mohemm_affective'] = cIsMohemmAffective;
|
||||
data['c_startdate'] = cStartdate;
|
||||
data['c_enddate'] = cEnddate;
|
||||
data['c_url'] = cUrl;
|
||||
data['c_token'] = token;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class EtqanGetEmployeeIncidentRequestModel {
|
||||
List<EtqanGetIncidentRequestResponse>? etqanGetIncidentRequestResponse;
|
||||
|
||||
EtqanGetEmployeeIncidentRequestModel({
|
||||
this.etqanGetIncidentRequestResponse,
|
||||
});
|
||||
|
||||
factory EtqanGetEmployeeIncidentRequestModel.fromRawJson(String str) => EtqanGetEmployeeIncidentRequestModel.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory EtqanGetEmployeeIncidentRequestModel.fromJson(Map<String, dynamic> json) => EtqanGetEmployeeIncidentRequestModel(
|
||||
etqanGetIncidentRequestResponse: json["ETQAN_GetIncidentRequest_Response"] == null ? [] : List<EtqanGetIncidentRequestResponse>.from(json["ETQAN_GetIncidentRequest_Response"]!.map((x) => EtqanGetIncidentRequestResponse.fromJson(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"ETQAN_GetIncidentRequest_Response": etqanGetIncidentRequestResponse == null ? [] : List<dynamic>.from(etqanGetIncidentRequestResponse!.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class EtqanGetIncidentRequestResponse {
|
||||
String? key;
|
||||
String? keyAr;
|
||||
String? value;
|
||||
String? valueAr;
|
||||
List<String>? subValues;
|
||||
|
||||
EtqanGetIncidentRequestResponse({
|
||||
this.key,
|
||||
this.keyAr,
|
||||
this.value,
|
||||
this.valueAr,
|
||||
this.subValues,
|
||||
});
|
||||
|
||||
factory EtqanGetIncidentRequestResponse.fromRawJson(String str) => EtqanGetIncidentRequestResponse.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory EtqanGetIncidentRequestResponse.fromJson(Map<String, dynamic> json) => EtqanGetIncidentRequestResponse(
|
||||
key: json["key"],
|
||||
keyAr: json["keyAr"],
|
||||
value: json["value"],
|
||||
valueAr: json["valueAr"],
|
||||
subValues: json["subValues"] == null ? [] : List<String>.from(json["subValues"]!.map((x) => x)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"key": key,
|
||||
"keyAr": keyAr,
|
||||
"value": value,
|
||||
"valueAr": valueAr,
|
||||
"subValues": subValues == null ? [] : List<dynamic>.from(subValues!.map((x) => x)),
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class EtqanGetEmployeeRequestsModel {
|
||||
List<EtqanGetEmployeeOvrRequestsResponse>? etqanGetEmployeeOvrRequestsResponse;
|
||||
|
||||
EtqanGetEmployeeRequestsModel({
|
||||
this.etqanGetEmployeeOvrRequestsResponse,
|
||||
});
|
||||
|
||||
factory EtqanGetEmployeeRequestsModel.fromRawJson(String str) => EtqanGetEmployeeRequestsModel.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory EtqanGetEmployeeRequestsModel.fromJson(Map<String, dynamic> json) => EtqanGetEmployeeRequestsModel(
|
||||
etqanGetEmployeeOvrRequestsResponse: json["ETQAN_GetEmployeeOVRRequests_Response"] == null ? [] : List<EtqanGetEmployeeOvrRequestsResponse>.from(json["ETQAN_GetEmployeeOVRRequests_Response"]!.map((x) => EtqanGetEmployeeOvrRequestsResponse.fromJson(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"ETQAN_GetEmployeeOVRRequests_Response": etqanGetEmployeeOvrRequestsResponse == null ? [] : List<dynamic>.from(etqanGetEmployeeOvrRequestsResponse!.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class EtqanGetEmployeeOvrRequestsResponse {
|
||||
String? createdDate;
|
||||
String? description;
|
||||
String? id;
|
||||
bool? isAnonymous;
|
||||
String? status;
|
||||
String? statusAr;
|
||||
String? ticketNumber;
|
||||
|
||||
EtqanGetEmployeeOvrRequestsResponse({
|
||||
this.createdDate,
|
||||
this.description,
|
||||
this.id,
|
||||
this.isAnonymous,
|
||||
this.status,
|
||||
this.statusAr,
|
||||
this.ticketNumber,
|
||||
});
|
||||
|
||||
factory EtqanGetEmployeeOvrRequestsResponse.fromRawJson(String str) => EtqanGetEmployeeOvrRequestsResponse.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory EtqanGetEmployeeOvrRequestsResponse.fromJson(Map<String, dynamic> json) => EtqanGetEmployeeOvrRequestsResponse(
|
||||
createdDate: json["createdDate"],
|
||||
description: json["description"],
|
||||
id: json["id"],
|
||||
isAnonymous: json["isAnonymous"],
|
||||
status: json["status"],
|
||||
statusAr: json["statusAr"],
|
||||
ticketNumber: json["ticketNumber"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"createdDate": createdDate,
|
||||
"description": description,
|
||||
"id": id,
|
||||
"isAnonymous": isAnonymous,
|
||||
"status": status,
|
||||
"statusAr": statusAr,
|
||||
"ticketNumber": ticketNumber,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class EtqanGetProjectsModel {
|
||||
List<EtqanGetProjectsResponse>? etqanGetProjectsResponse;
|
||||
|
||||
EtqanGetProjectsModel({
|
||||
this.etqanGetProjectsResponse,
|
||||
});
|
||||
|
||||
factory EtqanGetProjectsModel.fromRawJson(String str) => EtqanGetProjectsModel.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory EtqanGetProjectsModel.fromJson(Map<String, dynamic> json) => EtqanGetProjectsModel(
|
||||
etqanGetProjectsResponse: json["ETQAN_GetProjects_Response"] == null ? [] : List<EtqanGetProjectsResponse>.from(json["ETQAN_GetProjects_Response"]!.map((x) => EtqanGetProjectsResponse.fromJson(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"ETQAN_GetProjects_Response": etqanGetProjectsResponse == null ? [] : List<dynamic>.from(etqanGetProjectsResponse!.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class EtqanGetProjectsResponse {
|
||||
String? projectAr;
|
||||
String? projectCode;
|
||||
String? projectEn;
|
||||
int? projectId;
|
||||
|
||||
EtqanGetProjectsResponse({
|
||||
this.projectAr,
|
||||
this.projectCode,
|
||||
this.projectEn,
|
||||
this.projectId,
|
||||
});
|
||||
|
||||
factory EtqanGetProjectsResponse.fromRawJson(String str) => EtqanGetProjectsResponse.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory EtqanGetProjectsResponse.fromJson(Map<String, dynamic> json) => EtqanGetProjectsResponse(
|
||||
projectAr: json["projectAr"],
|
||||
projectCode: json["projectCode"],
|
||||
projectEn: json["projectEn"],
|
||||
projectId: json["projectId"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"projectAr": projectAr,
|
||||
"projectCode": projectCode,
|
||||
"projectEn": projectEn,
|
||||
"projectId": projectId,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,262 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/api/etqan_ovr_api_client.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart';
|
||||
import 'package:speech_to_text/speech_recognition_result.dart';
|
||||
import 'package:speech_to_text/speech_to_text.dart';
|
||||
|
||||
class EtqanOvrProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
// Home screen state
|
||||
List<EtqanGetProjectsResponse>? getEtqanProjectsList;
|
||||
List<EtqanGetEmployeeOvrRequestsResponse>? getEtqanEmployeeRequestsList;
|
||||
List<EtqanGetIncidentRequestResponse>? getEtqanEmployeeIncidnetRequest;
|
||||
bool isLoading = false;
|
||||
String? _ticketId;
|
||||
String? _currentStatus;
|
||||
String? _currentStatusAr;
|
||||
bool? _currentIsAnonymous;
|
||||
|
||||
// Speech to text state
|
||||
final SpeechToText _speechToText = SpeechToText();
|
||||
bool _speechEnabled = false;
|
||||
bool _isListening = false;
|
||||
String _currentLocaleId = '';
|
||||
TextEditingController? _currentController;
|
||||
String _baseText = ''; // Text that was there when we started listening
|
||||
String _currentSessionText = ''; // Text captured in current listening session
|
||||
|
||||
// Getters for speech state
|
||||
bool get speechEnabled => _speechEnabled;
|
||||
|
||||
bool get isListening => _isListening;
|
||||
|
||||
// Getter for ticketId
|
||||
String get getTicketId => _ticketId ?? '';
|
||||
String get getCurrentStatus => _currentStatus ?? '';
|
||||
String get getCurrentStatusAr => _currentStatusAr ?? '';
|
||||
bool get getCurrentIsAnonymous => _currentIsAnonymous ?? false;
|
||||
|
||||
// Setter for ticketId with status and isAnonymous
|
||||
void setTicketId(String ticketId, {String? status, String? statusAr, bool? isAnonymous}) {
|
||||
_ticketId = ticketId;
|
||||
_currentStatus = status;
|
||||
_currentStatusAr = statusAr;
|
||||
_currentIsAnonymous = isAnonymous;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Create request screen state
|
||||
EtqanGetProjectsResponse? selectedProject;
|
||||
String? selectedEmployeeNumber;
|
||||
File? attachment;
|
||||
String? attachmentBase64;
|
||||
String description = '';
|
||||
bool isAnonymous = false;
|
||||
|
||||
/// Initialize speech recognition - call this once when screen loads
|
||||
Future<void> initSpeech() async {
|
||||
_speechEnabled = await _speechToText.initialize(
|
||||
onStatus: (String status) {
|
||||
print('Speech status: $status, isListening: $_isListening');
|
||||
// If user wants to keep listening but speech stopped, restart it
|
||||
if ((status == 'done' || status == 'notListening') && _isListening && _currentController != null) {
|
||||
// Save the finalized text before restarting
|
||||
if (_currentSessionText.isNotEmpty) {
|
||||
_baseText = _currentController!.text.trim();
|
||||
_currentSessionText = '';
|
||||
}
|
||||
|
||||
// Automatically restart listening after a brief pause
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (_isListening && _currentController != null) {
|
||||
_startListeningInternal(_currentLocaleId, _currentController!);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Internal method to start listening
|
||||
Future<void> _startListeningInternal(String localeId, TextEditingController controller) async {
|
||||
if (!_speechEnabled) return;
|
||||
|
||||
await _speechToText.listen(
|
||||
onResult: (SpeechRecognitionResult result) {
|
||||
_currentSessionText = result.recognizedWords;
|
||||
|
||||
// Combine base text with current session text
|
||||
String combinedText = _baseText.isEmpty
|
||||
? _currentSessionText
|
||||
: _baseText + (_baseText.endsWith(' ') ? '' : ' ') + _currentSessionText;
|
||||
|
||||
description = combinedText;
|
||||
controller.text = combinedText;
|
||||
controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
|
||||
notifyListeners();
|
||||
},
|
||||
localeId: localeId,
|
||||
listenFor: const Duration(seconds: 60),
|
||||
pauseFor: const Duration(seconds: 10),
|
||||
listenOptions: SpeechListenOptions(
|
||||
partialResults: true,
|
||||
listenMode: ListenMode.confirmation,
|
||||
cancelOnError: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Start listening for speech input
|
||||
Future<void> startListening(String localeId, TextEditingController controller) async {
|
||||
_isListening = true;
|
||||
_currentLocaleId = localeId;
|
||||
_currentController = controller;
|
||||
_baseText = controller.text.trim(); // Save existing text
|
||||
_currentSessionText = '';
|
||||
notifyListeners();
|
||||
await _startListeningInternal(localeId, controller);
|
||||
}
|
||||
|
||||
/// Stop listening for speech input
|
||||
Future<void> stopListening() async {
|
||||
_isListening = false;
|
||||
_currentLocaleId = '';
|
||||
_currentController = null;
|
||||
_baseText = '';
|
||||
_currentSessionText = '';
|
||||
await _speechToText.stop();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Toggle listening state
|
||||
Future<void> toggleListening(String localeId, TextEditingController controller) async {
|
||||
if (_isListening) {
|
||||
await stopListening();
|
||||
} else {
|
||||
await startListening(localeId, controller);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Etqan Projects
|
||||
Future<void> fetchEtqanProjects(BuildContext context) async {
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
Utils.showLoading(context);
|
||||
getEtqanProjectsList?.clear();
|
||||
getEtqanProjectsList = await EtqanApiClient().getEtqanProjects();
|
||||
Utils.hideLoading(context);
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Fetch Etqan Employee Requests
|
||||
Future<void> fetchEtqanEmployeeRequests(BuildContext context) async {
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
Utils.showLoading(context);
|
||||
getEtqanEmployeeRequestsList?.clear();
|
||||
getEtqanEmployeeRequestsList = await EtqanApiClient().getEmployeeEtqanRequests();
|
||||
Utils.hideLoading(context);
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Fetch Etqan Employee Incident Report
|
||||
Future<void> fetchEtqanEmployeeIncidentRequests(BuildContext context, String ticketNo) async {
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
Utils.showLoading(context);
|
||||
getEtqanEmployeeIncidnetRequest?.clear();
|
||||
getEtqanEmployeeIncidnetRequest = await EtqanApiClient().getEtqanIncidentRequests(ticketNo);
|
||||
Utils.hideLoading(context);
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Set selected project
|
||||
void setSelectedProject(EtqanGetProjectsResponse? project) {
|
||||
selectedProject = project;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Set selected employee number
|
||||
void setSelectedEmployeeNumber(String? employeeNumber) {
|
||||
selectedEmployeeNumber = employeeNumber;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Set attachment with base64
|
||||
void setAttachment(File file, String base64) {
|
||||
attachment = file;
|
||||
attachmentBase64 = base64;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Remove attachment
|
||||
void removeAttachment() {
|
||||
attachment = null;
|
||||
attachmentBase64 = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Set description
|
||||
void setDescription(String value) {
|
||||
description = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Validate description
|
||||
bool isDescriptionValid() {
|
||||
return description.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
// Set isAnonymous
|
||||
void setIsAnonymous(bool value) {
|
||||
isAnonymous = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Clear create request form
|
||||
void clearCreateRequestForm() {
|
||||
selectedProject = null;
|
||||
selectedEmployeeNumber = null;
|
||||
attachment = null;
|
||||
attachmentBase64 = null;
|
||||
description = '';
|
||||
isAnonymous = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Submit request
|
||||
Future<Map<String, dynamic>?> submitRequest(BuildContext context) async {
|
||||
try {
|
||||
Utils.showLoading(context);
|
||||
Map<String, dynamic>? response = await EtqanApiClient().createRequest(description, selectedProject?.projectId, attachmentBase64, isAnonymous);
|
||||
Utils.hideLoading(context);
|
||||
if (response != null) {
|
||||
getEtqanEmployeeRequestsList = await EtqanApiClient().getEmployeeEtqanRequests();
|
||||
notifyListeners();
|
||||
return response;
|
||||
}
|
||||
return null;
|
||||
} catch (ex) {
|
||||
Utils.hideLoading(context);
|
||||
Utils.handleException(ex, context, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_isListening) {
|
||||
_speechToText.stop();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,360 @@
|
||||
import 'dart:io';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/config/routes.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_getprojects_model.dart';
|
||||
import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart';
|
||||
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
||||
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
|
||||
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
|
||||
import 'package:mohem_flutter_app/widgets/image_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class EtqanOvrCreateRequest extends StatefulWidget {
|
||||
const EtqanOvrCreateRequest({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EtqanOvrCreateRequestState createState() => _EtqanOvrCreateRequestState();
|
||||
}
|
||||
|
||||
class _EtqanOvrCreateRequestState extends State<EtqanOvrCreateRequest> {
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
var provider = context.read<EtqanOvrProviderModel>();
|
||||
provider.initSpeech();
|
||||
provider.fetchEtqanProjects(context);
|
||||
provider.clearCreateRequestForm();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Stop listening if active
|
||||
context.read<EtqanOvrProviderModel>().stopListening();
|
||||
_descriptionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBarWidget(context, title: LocaleKeys.reportIncident.tr(), showHomeButton: false),
|
||||
body: Consumer<EtqanOvrProviderModel>(
|
||||
builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
(ListView(
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(21),
|
||||
children: <Widget>[
|
||||
_buildDropdownField(
|
||||
label: LocaleKeys.project.tr(),
|
||||
hint: LocaleKeys.selectProject.tr(),
|
||||
value: _getProjectDisplayName(provider.selectedProject),
|
||||
onTap: () => _showProjectSelectionSheet(context, provider),
|
||||
isMandatory: true,
|
||||
),
|
||||
16.height,
|
||||
// _buildDropdownField(
|
||||
// label: 'Employee Number',
|
||||
// hint: 'Write a message',
|
||||
// value: provider.selectedEmployeeNumber,
|
||||
// onTap: () {
|
||||
// // TODO: Show employee selection dialog
|
||||
// },
|
||||
// ),
|
||||
// 16.height,
|
||||
_buildAttachmentSection(provider),
|
||||
16.height,
|
||||
_buildDescriptionField(provider),
|
||||
8.height,
|
||||
_buildAnonymousCheckbox(provider),
|
||||
24.height,
|
||||
],
|
||||
)).expanded,
|
||||
DefaultButton(LocaleKeys.submit.tr(), () async {
|
||||
provider.setDescription(_descriptionController.text);
|
||||
if (provider.selectedProject == null) {
|
||||
Utils.showErrorDialog(
|
||||
context: context,
|
||||
message: LocaleKeys.pleaseSelectProject.tr(),
|
||||
onOkTapped: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!provider.isDescriptionValid()) {
|
||||
Utils.showErrorDialog(
|
||||
context: context,
|
||||
message: LocaleKeys.pleaseEnterDescription.tr(),
|
||||
onOkTapped: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
Map<String, dynamic>? ticketInfo = await provider.submitRequest(context);
|
||||
if (ticketInfo != null) {
|
||||
String ticketNumber = ticketInfo['ticketNumber'] ?? '';
|
||||
String ticketId = ticketInfo['id'] ?? '';
|
||||
// String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber\n${LocaleKeys.ticketId.tr()}: $ticketId';
|
||||
String successMessage = '${LocaleKeys.requestCreatedSuccessfully.tr()}\n\n${LocaleKeys.ticketNumber.tr()}: $ticketNumber';
|
||||
Utils.showErrorDialog(
|
||||
context: context,
|
||||
message: successMessage,
|
||||
onOkTapped: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
// Navigator.popAndPushNamed(context, AppRoutes.etqanOvr);
|
||||
},
|
||||
onCloseTap: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
// Navigator.popAndPushNamed(context, AppRoutes.etqanOvr);
|
||||
},
|
||||
);
|
||||
}
|
||||
}).insideContainer,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String? _getProjectDisplayName(EtqanGetProjectsResponse? project) {
|
||||
if (project == null) return null;
|
||||
return AppState().isArabic(context) ? project.projectAr : project.projectEn;
|
||||
}
|
||||
|
||||
void _showProjectSelectionSheet(BuildContext context, EtqanOvrProviderModel provider) {
|
||||
if (provider.getEtqanProjectsList == null || provider.getEtqanProjectsList!.isEmpty) {
|
||||
return;
|
||||
}
|
||||
showMyBottomSheet(
|
||||
context,
|
||||
callBackFunc: () {},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text(LocaleKeys.selectProject.tr(), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xff2B353E))),
|
||||
),
|
||||
const Divider(),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: provider.getEtqanProjectsList!.length,
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(height: 1),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
EtqanGetProjectsResponse project = provider.getEtqanProjectsList![index];
|
||||
bool isSelected = provider.selectedProject?.projectId == project.projectId;
|
||||
return ListTile(
|
||||
title: Text(
|
||||
AppState().isArabic(context) ? (project.projectAr ?? '') : (project.projectEn ?? ''),
|
||||
style: TextStyle(fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, color: isSelected ? MyColors.gradiantEndColor : const Color(0xff2B353E)),
|
||||
),
|
||||
trailing: isSelected ? const Icon(Icons.check, color: MyColors.gradiantEndColor) : null,
|
||||
onTap: () {
|
||||
provider.setSelectedProject(project);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
16.height,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownField({required String label, required String hint, String? value, required VoidCallback onTap, bool isMandatory = false}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(label, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.44)),
|
||||
if (isMandatory) const Text(' *', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.red, letterSpacing: -0.44)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(value ?? hint, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: value != null ? const Color(0xff2B353E) : const Color(0xff575757), letterSpacing: -0.56)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.keyboard_arrow_down, color: Color(0xff575757)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAttachmentSection(EtqanOvrProviderModel provider) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(LocaleKeys.supportingDocument.tr(), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2B353E))),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(LocaleKeys.attachments.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xff2B353E))),
|
||||
if (provider.attachment == null)
|
||||
InkWell(
|
||||
onTap: () {
|
||||
ImageOptions.showImageOptionsNew(context, true, (String base64, File file) {
|
||||
provider.setAttachment(file, base64);
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
decoration: BoxDecoration(color: MyColors.gradiantEndColor, borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(LocaleKeys.add.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (provider.attachment != null) ...<Widget>[
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(Icons.attach_file, size: 18, color: Color(0xff575757)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(provider.attachment!.path.split('/').last, style: const TextStyle(fontSize: 12, color: Color(0xff575757)), overflow: TextOverflow.ellipsis)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
provider.removeAttachment();
|
||||
},
|
||||
child: const Icon(Icons.close, size: 18, color: Colors.red),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDescriptionField(EtqanOvrProviderModel provider) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white, border: Border.all(color: const Color(0xffefefef), width: 1)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(LocaleKeys.description.tr(), style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.44)),
|
||||
const Text(' *', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.red, letterSpacing: -0.44)),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
onTap:
|
||||
provider.speechEnabled
|
||||
? () {
|
||||
String localeId = AppState().isArabic(context) ? 'ar_SA' : 'en_US';
|
||||
provider.toggleListening(localeId, _descriptionController);
|
||||
}
|
||||
: null,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(color: provider.isListening ? MyColors.gradiantEndColor.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(20)),
|
||||
child: Icon(
|
||||
provider.isListening ? Icons.mic : Icons.mic_none,
|
||||
color: provider.isListening ? MyColors.gradiantEndColor : (provider.speechEnabled ? MyColors.grey57Color : Colors.grey.shade300),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (provider.isListening)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(width: 8, height: 8, decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(4))),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Listening...', style: TextStyle(fontSize: 12, color: MyColors.gradiantEndColor, fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
maxLines: 4,
|
||||
onChanged: (String value) => provider.setDescription(value),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xff2B353E), letterSpacing: -0.44),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
hintText: LocaleKeys.writeAMessage.tr(),
|
||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xff575757), letterSpacing: -0.56),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAnonymousCheckbox(EtqanOvrProviderModel provider) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
provider.setIsAnonymous(!provider.isAnonymous);
|
||||
},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: provider.isAnonymous,
|
||||
onChanged: (bool? value) {
|
||||
if (value != null) {
|
||||
provider.setIsAnonymous(value);
|
||||
}
|
||||
},
|
||||
activeColor: MyColors.gradiantEndColor,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
|
||||
Expanded(child: Text(LocaleKeys.submitAsAnonymous.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xff2B353E), letterSpacing: -0.44))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
import 'package:easy_localization/easy_localization.dart' as lclize;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/config/routes.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_requests_model.dart';
|
||||
import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
||||
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/src/intl/text_direction.dart' as intl;
|
||||
import 'package:sizer/sizer.dart';
|
||||
|
||||
class EtqanOvrHome extends StatefulWidget {
|
||||
const EtqanOvrHome({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EtqanOvrHomeState createState() => _EtqanOvrHomeState();
|
||||
}
|
||||
|
||||
class _EtqanOvrHomeState extends State<EtqanOvrHome> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<EtqanOvrProviderModel>().fetchEtqanEmployeeRequests(context);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getStatusColor(String status) {
|
||||
String statusLower = status.toLowerCase();
|
||||
if (statusLower.contains('open') || statusLower.contains('مفتوح')) {
|
||||
return const Color(0xff2196F3); // Blue for open
|
||||
} else if (statusLower.contains('progress') || statusLower.contains('قيد التنفيذ')) {
|
||||
return const Color(0xffFF9800); // Orange for in progress
|
||||
} else if (statusLower.contains('closed') || statusLower.contains('مغلق')) {
|
||||
return const Color(0xff4CAF50); // Green for closed
|
||||
}
|
||||
return MyColors.grey70Color; // Default gray
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBarWidget(context, title: LocaleKeys.myIncidents.tr()),
|
||||
body: Consumer<EtqanOvrProviderModel>(
|
||||
builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
(provider.getEtqanEmployeeRequestsList == null || provider.getEtqanEmployeeRequestsList!.isEmpty
|
||||
? Utils.getNoDataWidget(context)
|
||||
: ListView.separated(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(20),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
EtqanGetEmployeeOvrRequestsResponse data = provider.getEtqanEmployeeRequestsList![index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<EtqanOvrProviderModel>().setTicketId(data.ticketNumber ?? '', status: data.status, statusAr: data.statusAr, isAnonymous: data.isAnonymous);
|
||||
Navigator.pushNamed(context, AppRoutes.etqanGetIncidientRequest);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xffefefef), width: 1)),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
if (data.status != null && data.status!.isNotEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(data.status ?? "").withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: _getStatusColor(data.status ?? "").withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: (AppState().isArabic(context) ? (data.statusAr ?? "") : (data.status ?? "")).toText11(
|
||||
color: _getStatusColor(data.status ?? ""),
|
||||
isBold: true,
|
||||
),
|
||||
),
|
||||
if (data.isAnonymous == true)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: MyColors.grey70Color.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: MyColors.grey70Color.withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.visibility_off, size: 12, color: MyColors.grey70Color),
|
||||
const SizedBox(width: 4),
|
||||
(AppState().isArabic(context) ? "مجهول" : "Anonymous").toText11(color: MyColors.grey70Color, isBold: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
8.height,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
(data.ticketNumber ?? "").toText14(isBold: true, color: MyColors.darkTextColor),
|
||||
Directionality(textDirection: TextDirection.ltr, child: (data.createdDate!.split(" ").first ?? "").toText12(color: MyColors.grey70Color)),
|
||||
],
|
||||
),
|
||||
8.height,
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [(data.description ?? "").toText12(color: MyColors.grey57Color).expanded]),
|
||||
3.height,
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
RotatedBox(
|
||||
quarterTurns: AppState().isArabic(context) ? 2 : 0,
|
||||
child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.grey70Color, width: 16, height: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => 12.height,
|
||||
itemCount: provider.getEtqanEmployeeRequestsList!.length,
|
||||
))
|
||||
.expanded,
|
||||
DefaultButton(LocaleKeys.reportIncident.tr(), () async {
|
||||
await Navigator.pushNamed(context, AppRoutes.etqanCreateRequest);
|
||||
// getOpenTickets();
|
||||
}).insideContainer,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/models/etqan_ovr/etqan_get_employee_incident_report.dart';
|
||||
import 'package:mohem_flutter_app/provider/etqan_ovr_provider.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class EtqanOvrRequestDetailed extends StatefulWidget {
|
||||
const EtqanOvrRequestDetailed({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EtqanOvrRequestDetailedState createState() => _EtqanOvrRequestDetailedState();
|
||||
}
|
||||
|
||||
class _EtqanOvrRequestDetailedState extends State<EtqanOvrRequestDetailed> {
|
||||
List<String> get fieldOrder => ['Status', 'Project', 'Created', 'Is Anonymous', 'Occurrence Time', 'OVR Reference No', 'Involved Employee Number', 'Description'];
|
||||
|
||||
List<String> get hiddenFields => ['OVR Id'];
|
||||
|
||||
Color _getStatusColor(String status) {
|
||||
String statusLower = status.toLowerCase();
|
||||
if (statusLower.contains('open') || statusLower.contains('مفتوح')) {
|
||||
return const Color(0xff2196F3); // Blue for open
|
||||
} else if (statusLower.contains('progress') || statusLower.contains('قيد التنفيذ')) {
|
||||
return const Color(0xffFF9800); // Orange for in progress
|
||||
} else if (statusLower.contains('closed') || statusLower.contains('مغلق')) {
|
||||
return const Color(0xff4CAF50); // Green for closed
|
||||
}
|
||||
return MyColors.grey70Color; // Default gray
|
||||
}
|
||||
|
||||
Future<void> _viewAttachment(BuildContext context, String base64Data) async {
|
||||
try {
|
||||
Uint8List bytes = base64.decode(base64Data);
|
||||
String dir = (await getApplicationDocumentsDirectory()).path;
|
||||
String ext = 'pdf';
|
||||
if (base64Data.startsWith('/9j/')) {
|
||||
ext = 'jpg';
|
||||
} else if (base64Data.startsWith('iVBORw0KGgo')) {
|
||||
ext = 'png';
|
||||
}
|
||||
File file = File("$dir/${DateTime.now().millisecondsSinceEpoch}.$ext");
|
||||
await file.writeAsBytes(bytes);
|
||||
OpenFilex.open(file.path);
|
||||
} catch (e) {
|
||||
Utils.showToast("Cannot open attachment.");
|
||||
}
|
||||
}
|
||||
|
||||
List<EtqanGetIncidentRequestResponse> _getOrderedList(List<EtqanGetIncidentRequestResponse> list, EtqanOvrProviderModel provider) {
|
||||
List<EtqanGetIncidentRequestResponse> orderedList = [];
|
||||
|
||||
List<EtqanGetIncidentRequestResponse> filteredList = list.where((EtqanGetIncidentRequestResponse item) => !hiddenFields.any((f) => f.toLowerCase() == item.key?.toLowerCase())).toList();
|
||||
|
||||
for (String fieldName in fieldOrder) {
|
||||
// Check if this is Status or Is Anonymous - inject from provider
|
||||
if (fieldName.toLowerCase() == 'status' && provider.getCurrentStatus.isNotEmpty) {
|
||||
orderedList.add(EtqanGetIncidentRequestResponse(
|
||||
key: 'Status',
|
||||
keyAr: 'الحالة',
|
||||
value: provider.getCurrentStatus,
|
||||
valueAr: provider.getCurrentStatusAr.isNotEmpty ? provider.getCurrentStatusAr : provider.getCurrentStatus,
|
||||
));
|
||||
} else if (fieldName.toLowerCase() == 'is anonymous') {
|
||||
orderedList.add(EtqanGetIncidentRequestResponse(
|
||||
key: 'Is Anonymous',
|
||||
keyAr: 'مجهول',
|
||||
value: provider.getCurrentIsAnonymous.toString(),
|
||||
valueAr: provider.getCurrentIsAnonymous.toString(),
|
||||
));
|
||||
} else {
|
||||
// Regular field from API
|
||||
var item = filteredList.firstWhere((e) => e.key?.toLowerCase() == fieldName.toLowerCase(), orElse: () => EtqanGetIncidentRequestResponse());
|
||||
if (item.key != null) {
|
||||
orderedList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var item in filteredList) {
|
||||
if (!fieldOrder.any((f) => f.toLowerCase() == item.key?.toLowerCase())) {
|
||||
orderedList.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return orderedList;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
EtqanOvrProviderModel provider = context.read<EtqanOvrProviderModel>();
|
||||
provider.fetchEtqanEmployeeIncidentRequests(context, provider.getTicketId);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBarWidget(context, title: LocaleKeys.incidentDetails.tr()),
|
||||
body: Consumer<EtqanOvrProviderModel>(
|
||||
builder: (BuildContext context, EtqanOvrProviderModel provider, Widget? child) {
|
||||
if (provider.getEtqanEmployeeIncidnetRequest == null || provider.getEtqanEmployeeIncidnetRequest!.isEmpty) {
|
||||
return Utils.getNoDataWidget(context);
|
||||
}
|
||||
|
||||
List<EtqanGetIncidentRequestResponse> orderedList = _getOrderedList(provider.getEtqanEmployeeIncidnetRequest!, provider);
|
||||
|
||||
return SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: const Color(0xffefefef), width: 1),
|
||||
boxShadow: <BoxShadow>[BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:
|
||||
orderedList.asMap().entries.map((MapEntry<int, EtqanGetIncidentRequestResponse> entry) {
|
||||
int index = entry.key;
|
||||
EtqanGetIncidentRequestResponse data = entry.value;
|
||||
bool isLast = index == orderedList.length - 1;
|
||||
bool isAttachment = data.key?.toLowerCase() == 'attachment';
|
||||
bool isStatus = data.key?.toLowerCase() == 'status';
|
||||
bool isAnonymous = data.key?.toLowerCase() == 'is anonymous';
|
||||
|
||||
String displayKey = context.locale.languageCode == "ar" ? (data.keyAr ?? data.key ?? "") : (data.key ?? "");
|
||||
String displayValue = context.locale.languageCode == "ar" ? (data.valueAr ?? data.value ?? "") : (data.value ?? "");
|
||||
|
||||
bool hasSubValues = data.subValues != null && data.subValues!.isNotEmpty;
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(flex: 2, child: displayKey.toText12(isBold: true, color: MyColors.grey57Color)),
|
||||
8.width,
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: isStatus
|
||||
? displayValue.toText14(
|
||||
color: _getStatusColor(displayValue),
|
||||
isBold: true,
|
||||
)
|
||||
: isAnonymous
|
||||
? (displayValue.toLowerCase() == 'true' || displayValue.toLowerCase() == 'yes' || displayValue == '1'
|
||||
? (context.locale.languageCode == "ar" ? "نعم" : "Yes")
|
||||
: (context.locale.languageCode == "ar" ? "لا" : "No")
|
||||
).toText14(color: MyColors.darkTextColor)
|
||||
: isAttachment && hasSubValues
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: data.subValues!.asMap().entries.map((entry) {
|
||||
int subIndex = entry.key;
|
||||
String base64Data = entry.value;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: subIndex < data.subValues!.length - 1 ? 8.0 : 0),
|
||||
child: GestureDetector(
|
||||
onTap: () => _viewAttachment(context, base64Data),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: context.locale.languageCode == "ar" ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
data.subValues!.length > 1 ? '${LocaleKeys.viewAttachment.tr()} ${subIndex + 1}' : LocaleKeys.viewAttachment.tr(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: MyColors.green9CColor,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: MyColors.green9CColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
: hasSubValues
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (displayValue.isNotEmpty) displayValue.toText14(color: MyColors.darkTextColor),
|
||||
if (displayValue.isNotEmpty) 8.height,
|
||||
...data.subValues!
|
||||
.map(
|
||||
(subValue) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [const Text("• ", style: TextStyle(fontSize: 14)), Expanded(child: subValue.toText14(color: MyColors.darkTextColor))],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
)
|
||||
: (displayValue.isNotEmpty ? displayValue : "-").toText14(color: MyColors.darkTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!isLast) const Padding(padding: EdgeInsets.only(left: 14, right: 14), child: Divider(height: 1, thickness: 1, color: Color(0xffefefef))),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).paddingOnly(bottom: 20),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,333 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/classes/inAppWebView.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/models/dashboard/courses_response_model.dart';
|
||||
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
|
||||
import 'package:mohem_flutter_app/theme/colors.dart' as theme_colors;
|
||||
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
||||
import 'package:mohem_flutter_app/widgets/sso_webview_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
class CoursesScreen extends StatefulWidget {
|
||||
const CoursesScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_CoursesScreenState createState() => _CoursesScreenState();
|
||||
}
|
||||
|
||||
class _CoursesScreenState extends State<CoursesScreen> {
|
||||
late DashboardProviderModel data;
|
||||
final RefreshController _refreshController = RefreshController(initialRefresh: false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
data = Provider.of<DashboardProviderModel>(context, listen: false);
|
||||
_onRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
void _onRefresh() async {
|
||||
await data.fetchCourses(context);
|
||||
_refreshController.refreshCompleted();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBarWidget(
|
||||
context,
|
||||
title: LocaleKeys.courses.tr(),
|
||||
),
|
||||
body: Consumer<DashboardProviderModel>(
|
||||
builder: (context, provider, child) {
|
||||
return SmartRefresher(
|
||||
enablePullDown: true,
|
||||
enablePullUp: false,
|
||||
controller: _refreshController,
|
||||
onRefresh: _onRefresh,
|
||||
child: provider.isCoursesLoading
|
||||
? _buildShimmerLoading()
|
||||
: provider.coursesList.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildCoursesList(provider.coursesList),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmerLoading() {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(21),
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xff000000).withOpacity(.05),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, -3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: 20,
|
||||
width: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
).toShimmer(),
|
||||
12.height,
|
||||
Container(
|
||||
height: 14,
|
||||
width: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
).toShimmer(),
|
||||
const Spacer(),
|
||||
Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
).toShimmer(),
|
||||
],
|
||||
).paddingAll(14),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Utils.getNoDataWidget(context);
|
||||
}
|
||||
|
||||
Widget _buildCoursesList(List<CourseStatus> courses) {
|
||||
return ListView.separated(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.all(21),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _buildCourseCard(courses[index]);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => 12.height,
|
||||
itemCount: courses.length,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCourseCard(CourseStatus course) {
|
||||
bool isArabic = AppState().isArabic(context);
|
||||
String courseName = isArabic && course.cNameAr != null && course.cNameAr!.isNotEmpty
|
||||
? course.cNameAr!
|
||||
: course.cFullname ?? '';
|
||||
|
||||
String status = course.cStatus ?? 'Unknown';
|
||||
Color statusColor = _getStatusColor(status);
|
||||
|
||||
return InkWell(
|
||||
onTap: () => _openCourse(course.cUrl, course.token),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Course Title
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: MyColors.lightGreyEFColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.school_rounded,
|
||||
color: MyColors.gradiantStartColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
12.width,
|
||||
Expanded(
|
||||
child: courseName.toText14(isBold: true, color: MyColors.darkTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
12.height,
|
||||
// Status Badge
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
_getStatusIcon(status),
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
6.width,
|
||||
LocaleKeys.status.tr().toText11(color: MyColors.grey98Color),
|
||||
8.width,
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: statusColor.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: status.toText10(color: statusColor, isBold: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
10.height,
|
||||
// Start Date
|
||||
if (course.cEnddate != null && course.cEnddate!.isNotEmpty)
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.calendar_month_rounded,
|
||||
size: 16,
|
||||
color: MyColors.darkIconColor,
|
||||
),
|
||||
6.width,
|
||||
LocaleKeys.endDateT.tr().toText11(color: MyColors.grey98Color),
|
||||
8.width,
|
||||
Expanded(
|
||||
child: _formatDate(course.cEnddate!).toText11(
|
||||
color: MyColors.darkTextColor,
|
||||
isBold: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (course.cStartdate != null && course.cStartdate!.isNotEmpty) 14.height,
|
||||
// Open Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
LocaleKeys.open.tr().toText12(color: MyColors.gradiantStartColor, isUnderLine: true),
|
||||
6.width,
|
||||
RotatedBox(
|
||||
quarterTurns: AppState().isArabic(context) ? 2 : 4,
|
||||
child: SvgPicture.asset(
|
||||
"assets/images/arrow_next.svg",
|
||||
color: MyColors.gradiantStartColor,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).objectContainerView(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
IconData _getStatusIcon(String status) {
|
||||
if (status.toLowerCase().contains('passed')) {
|
||||
return Icons.check_circle_rounded;
|
||||
} else if (status.toLowerCase().contains('not passed') || status.toLowerCase().contains('failed')) {
|
||||
return Icons.cancel_rounded;
|
||||
} else if (status.toLowerCase().contains('in progress')) {
|
||||
return Icons.pending_rounded;
|
||||
} else {
|
||||
return Icons.info_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getStatusColor(String status) {
|
||||
if (status.toLowerCase().contains('passed') && !status.toLowerCase().contains('not passed')) {
|
||||
return Colors.green;
|
||||
} else if (status.toLowerCase().contains('not passed') || status.toLowerCase().contains('failed')) {
|
||||
return Colors.red;
|
||||
} else if (status.toLowerCase().contains('in progress')) {
|
||||
return Colors.yellow[700]!;
|
||||
} else {
|
||||
return MyColors.grey98Color;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? _parseDate(String? date) {
|
||||
if (date == null || date.isEmpty) return null;
|
||||
try {
|
||||
return DateTime.parse(date.replaceAll(' ', 'T'));
|
||||
} catch (e) {
|
||||
try {
|
||||
DateFormat format = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
return format.parse(date);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDate(String date) {
|
||||
DateTime? dateTime = _parseDate(date);
|
||||
if (dateTime == null) return date;
|
||||
|
||||
bool isArabic = AppState().isArabic(context);
|
||||
DateFormat format = DateFormat('dd MMM yyyy, hh:mm a', isArabic ? 'ar' : 'en');
|
||||
return format.format(dateTime);
|
||||
}
|
||||
|
||||
void _openCourse(String? url, String? jwtToken) {
|
||||
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => SsoLoginWebView(url: url, jwtToken: jwtToken)));
|
||||
//Utils.showToast(LocaleKeys.courseUrlNotAvailable.tr());
|
||||
|
||||
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// MyInAppBrowser browser = MyInAppBrowser(
|
||||
// onExitCallback: () {},
|
||||
// onLoadStartCallback: (String url) {},
|
||||
// );
|
||||
//
|
||||
// var settings = InAppBrowserClassSettings(
|
||||
// webViewSettings: InAppWebViewSettings(
|
||||
// useShouldOverrideUrlLoading: false,
|
||||
// transparentBackground: false,
|
||||
// ),
|
||||
// browserSettings: InAppBrowserSettings(
|
||||
// hideUrlBar: true,
|
||||
// hideTitleBar: false,
|
||||
// toolbarTopBackgroundColor: theme_colors.primaryColor,
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// browser.openUrlRequest(
|
||||
// urlRequest: URLRequest(url: WebUri(url)),
|
||||
// settings: settings,
|
||||
// );
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_refreshController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||