Browse Source

fix: eror di history dan 16kb compatibility play console

Yulian 1 tháng trước cách đây
mục cha
commit
6388617b0b

+ 9 - 0
android/app/build.gradle.kts

@@ -113,6 +113,9 @@ android {
         targetSdk = flutter.targetSdkVersion
         versionCode = flutter.versionCode
         versionName = flutter.versionName
+        ndk {
+            abiFilters += listOf("armeabi-v7a", "arm64-v8a")
+        }
     }
 
     signingConfigs {
@@ -132,6 +135,12 @@ android {
             signingConfig = signingConfigs.getByName("release")
         }
     }
+
+    packagingOptions {
+        jniLibs {
+            useLegacyPackaging = false
+        }
+    }
 }
 
 dependencies {

+ 129 - 0
lib/src/cubit/history_detail_cubit.dart

@@ -0,0 +1,129 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import '../repository/history_repository.dart';
+import '../utils/U.dart';
+
+class HistoryDetailState extends Equatable{
+  final Map<String, dynamic> data;
+  final Map<String, dynamic> asset;
+  final String fileName;
+  final bool isActive;
+  final bool timeLimit;
+  final bool showLoading;
+  final bool isLoading;
+
+  const HistoryDetailState({
+    this.data = const {},
+    this.asset = const {},
+    this.fileName = '',
+    this.isActive = false,
+    this.timeLimit = false,
+    this.showLoading = false,
+    this.isLoading = false,
+  });
+
+  HistoryDetailState copyWith({
+    Map<String, dynamic>? data,
+    Map<String, dynamic>? asset,
+    String? fileName,
+    bool? isActive,
+    bool? timeLimit,
+    bool? showLoading,
+    bool? isLoading,
+  }) {
+    return HistoryDetailState(
+      data: data ?? this.data,
+      asset: asset ?? this.asset,
+      fileName: fileName ?? this.fileName,
+      isActive: isActive ?? this.isActive,
+      timeLimit: timeLimit ?? this.timeLimit,
+      showLoading: showLoading ?? this.showLoading,
+      isLoading: isLoading ?? this.isLoading,
+    );
+  }
+
+  @override
+  // TODO: implement props
+  List<Object?> get props => [data, asset, fileName, isActive, timeLimit, showLoading, isLoading];
+
+}
+
+class OpenAttachment extends HistoryDetailState {}
+
+class NavigateBack extends HistoryDetailState {
+  final bool exc;
+  const NavigateBack(this.exc);
+}
+
+class HistoryDetailCubit extends Cubit<HistoryDetailState> {
+  final HistoryRepository historyRepo = HistoryRepository();
+
+  HistoryDetailCubit() : super(const HistoryDetailState());
+
+  Future<void> getData(String ticketNo, String locale) async {
+    Map<String, dynamic>? data = {};
+    Map<String, dynamic>? asset = {};
+    String baseUrl = await HistoryRepository().getBaseUrl();
+    bool isActive = false;
+
+    emit(state.copyWith(isLoading: true));
+
+    try {
+      data = await historyRepo.getDetail(ticketNo, locale);
+
+      if (data['_asset'] != null){
+        asset['name'] = data['_asset']['name'];
+        asset['description'] = data['_asset']['description'];
+        asset['_mobileImage'] = baseUrl + data['_asset']['image'];
+      }
+      isActive = data['currentState'] == 'DIPROSES' ||
+          data['currentState'] == 'DIANTRIKAN' ||
+          data['currentState'] == 'DIMULAI' ||
+          data['currentState'] == 'DISELESAIKAN';
+
+      emit(state.copyWith(data: data, asset: asset, isActive: isActive, isLoading: false));
+    } catch (e) {
+      data = {};
+      emit(state.copyWith(data: data, isLoading: false, timeLimit: true));
+    }
+
+  }
+
+  void setFileName(String fileName){
+    emit(state.copyWith(fileName: fileName));
+  }
+
+  Future<void> openAttachment(Map<String, dynamic> data) async {
+    String fileName = await HistoryRepository().getPath(state.data);
+
+    emit(state.copyWith(fileName: fileName));
+    emit(OpenAttachment());
+  }
+
+  Future<void> setAsset(String ticketNo, Map<String, dynamic> data) async {
+    Map<String, dynamic> asset = {};
+    String baseUrl = await HistoryRepository().getBaseUrl();
+
+    asset = await HistoryRepository().setAsset(ticketNo, data, baseUrl);
+
+    emit(state.copyWith(asset: asset));
+  }
+
+  Future<void> cancelRequest(list, controllerNote) async {
+    emit(state.copyWith(showLoading: true));
+
+    var data = {
+      'note': controllerNote.text.trim(),
+    };
+
+    var res = await HistoryRepository().cancelRequest(list['ticketNo'], data);
+    if(res != null){
+      emit(NavigateBack(true));
+      // navigateBack(context, exc: true);
+    }
+    emit(state.copyWith(showLoading: false));
+  }
+
+}

+ 29 - 24
lib/src/cubit/menu_history_cubit.dart

@@ -36,6 +36,8 @@ class MenuHistoryState extends Equatable {
   final List activeForum;
   final bool multiSelectMode;
   final List<int> selectedIndex;
+  final int activeIndex;
+
 
   const MenuHistoryState({
     this.activeTab = HistoryTab.ongoing,
@@ -43,6 +45,7 @@ class MenuHistoryState extends Equatable {
     this.activeForum = const [],
     this.multiSelectMode = false,
     this.selectedIndex = const [],
+    this.activeIndex = 0,
   });
 
   MenuHistoryState copyWith({
@@ -51,6 +54,7 @@ class MenuHistoryState extends Equatable {
     List? activeForum,
     bool? multiSelectMode,
     List<int>? selectedIndex,
+    int? activeIndex,
   }){
     return MenuHistoryState(
       activeTab: activeTab ?? this.activeTab,
@@ -58,11 +62,13 @@ class MenuHistoryState extends Equatable {
       activeForum: activeForum ?? this.activeForum,
       multiSelectMode: multiSelectMode ?? this.multiSelectMode,
       selectedIndex: selectedIndex ?? this.selectedIndex,
+      activeIndex: activeIndex ?? this.activeIndex,
     );
   }
 
   @override
-  List<Object?> get props => [activeTab, pendingData, activeForum, multiSelectMode, selectedIndex];
+  List<Object?> get props => [activeTab, pendingData, activeForum, multiSelectMode, selectedIndex, activeIndex];
+
 }
 
 class ReloadPage extends MenuHistoryState {}
@@ -77,12 +83,17 @@ class ShowError extends MenuHistoryState {
   const ShowError(this.error);
 }
 
+class TabChanged extends MenuHistoryState {
+  final int index;
+  const TabChanged(this.index);
+}
+
 class MenuHistoryCubit extends Cubit<MenuHistoryState> {
   MenuHistoryCubit() : super(MenuHistoryState());
   final SharedPreferencesManager sharedPreferencesManager = SharedPreferencesManager();
 
   void init() {
-    setActiveTab(HistoryTab.ongoing);
+    setActiveTab(state.activeTab);
     getActiveForum();
     getPendingData();
   }
@@ -99,7 +110,19 @@ class MenuHistoryCubit extends Cubit<MenuHistoryState> {
   }
 
   void setActiveTab(HistoryTab tab){
-    emit(state.copyWith(activeTab: tab));
+    if(state.activeTab == tab) return;
+
+    int index = 0;
+    switch(tab){
+      case HistoryTab.ongoing:
+        index = 0;
+        break;
+      case HistoryTab.done:
+        index = 1;
+        break;
+    }
+
+    emit(state.copyWith(activeTab: tab, activeIndex: index));
   }
 
   Future<void> getActiveForum() async {
@@ -149,26 +172,8 @@ class MenuHistoryCubit extends Cubit<MenuHistoryState> {
     }
   }
 
-  Future<void> requestAgain(Map<String, dynamic> data) async {
-    try {
-      String tenant = '';
-      String filter = '';
-
-      if ((data['requestGroupCode']).split(" ").length != 1) {
-        tenant = ',{"f":["tenantCode","EQ","${(data['requestGroupCode']).split(" ").first}"]}';
-      }
-      filter = '{"and":[{"f":["code","EQ","${data['requestCode']}"]}$tenant]}';
-
-      var res = await HistoryRepository().checkRequest(filter);
-      if (res.isNotEmpty) {
-        if (res.containsKey('_embedded')) {
-          emit(CreateNewRequest(res['_embedded']['requests'][0]));
-        } else {
-          emit(ShowError('reqCodeNotFound'.tr()));
-        }
-      }
-    } catch (e) {
-      emit(ShowError("Err: ${e.toString()}"));
-    }
+  void showError(String error){
+    emit(ShowError(error));
   }
+
 }

+ 22 - 7
lib/src/cubit/tab_history_cubit.dart

@@ -1,4 +1,5 @@
 import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import '../repository/history_repository.dart';
@@ -58,12 +59,15 @@ class OngoingCubit extends Cubit<TabState> {
   Future<void> loadOngoing() async {
     if(state.stopLoad) return;
 
-    if(state.data.isNotEmpty){
-      emit(state.copyWith(isLoading: false));
+    if(!state.reload) {
+      if (state.data.isNotEmpty) {
+        emit(state.copyWith(isLoading: false));
+      }
+      if (state.page == 0) emit(state.copyWith(isLoading: true));
+    } else {
+      emit(state.copyWith(reload: false));
     }
 
-    if(state.page == 0) emit(state.copyWith(isLoading: true));
-
     String filter = '';
     int page = 0;
     int size = 30;
@@ -90,9 +94,11 @@ class OngoingCubit extends Cubit<TabState> {
 
     // cache manager
     String key = '#ongoing#$filter';
-    var val = await CacheMan.readData(key);
-    if(val != null && page == 0){
-      emit(state.copyWith(data: val['data'], isLoading: false, stopLoad: stopLoad));
+    if(!state.reload) {
+      var val = await CacheMan.readData(key);
+      if (val != null && page == 0) {
+        emit(state.copyWith(data: val['data'], isLoading: false, stopLoad: stopLoad));
+      }
     }
 
     try {
@@ -124,6 +130,12 @@ class OngoingCubit extends Cubit<TabState> {
     emit(state.copyWith(page: state.page + 1));
     loadOngoing();
   }
+
+  void reloadPage(){
+    emit(state.copyWith(data: [], page: 0, stopLoad: false, isLoading: true, reload: true));
+    loadOngoing();
+  }
+
 }
 
 // # Cubit for Done Tab
@@ -142,6 +154,8 @@ class DoneCubit extends Cubit<TabState> {
       }
 
       if (state.page == 0) emit(state.copyWith(isLoading: true));
+    } else {
+      emit(state.copyWith(reload: false));
     }
 
     String filter = '';
@@ -208,6 +222,7 @@ class DoneCubit extends Cubit<TabState> {
 
   void reloadPage(){
     emit(state.copyWith(data: [], page: 0, stopLoad: false, isLoading: true, reload: true));
+    loadDone();
   }
 
   void setSatisfaction(int index, int tempRating) {

+ 86 - 0
lib/src/cubit/pickup_asset_cubit.dart

@@ -0,0 +1,86 @@
+// State
+import 'package:equatable/equatable.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import '../api/api_auth_provider.dart';
+
+class AssetState extends Equatable {
+  final int idSelected;
+  final List<dynamic> data;
+  final List<dynamic> result;
+  final String keyword;
+  final int page;
+  final bool isLoading;
+
+  const AssetState({
+    this.idSelected = -1,
+    this.data = const [],
+    this.result = const [],
+    this.keyword = '',
+    this.page = 0,
+    this.isLoading = true,
+  });
+
+  AssetState copyWith({
+    int? idSelected,
+    List<dynamic>? data,
+    List<dynamic>? result,
+    String? keyword,
+    int? page,
+    bool? isLoading,
+  }) {
+    return AssetState(
+      idSelected: idSelected ?? this.idSelected,
+      data: data ?? this.data,
+      result: result ?? this.result,
+      keyword: keyword ?? this.keyword,
+      page: page ?? this.page,
+      isLoading: isLoading ?? this.isLoading,
+    );
+  }
+
+  @override
+  List<Object?> get props => [idSelected, data, result, keyword, page, isLoading];
+}
+
+// Cubit
+class AssetCubit extends Cubit<AssetState> {
+  AssetCubit() : super(const AssetState());
+
+  final ApiAuthProvider _apiAuthProvider = ApiAuthProvider();
+
+  void getData({String keyword = '', int page = 0}) async {
+    String filter = keyword == '' ? '{"f":["1","EQ","1"]}' : '{"or":[{"f":["name","LIKE","%$keyword%"]},{"f":["description","LIKE","%$keyword%"]}]}';
+    final params = {
+      "select": "id, code, name, description, image, status",
+      "sort": "name:ASC",
+      "filter": filter,
+      "size": "10",
+      "page": "$page"
+    };
+    final rawData = await _apiAuthProvider.getData('/api/assets/findData', params);
+    final data = rawData['_embedded']['assets'];
+
+    if (data.isEmpty && page != 0) return;
+    var finalData = page == 0 ? data : state.data + data;
+
+    emit(state.copyWith(data: finalData, keyword: keyword, page: page, isLoading: false));
+  }
+
+  void toggleSelection(int id) {
+    final newID = id;
+    final res = state.data.where((element) => element['id'] == newID).toList();
+
+    emit(state.copyWith(idSelected: newID, result: res));
+  }
+
+  void search(String keyword) {
+    getData(keyword: keyword, page: 0);
+
+    emit(state.copyWith(isLoading: true, data: []));
+  }
+
+  void loadNextPage() {
+    getData(keyword: state.keyword, page: state.page + 1);
+  }
+}

+ 76 - 30
lib/src/layouts/components/rate_mission.dart

@@ -2,10 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:telnow_mobile_new/src/layouts/components/template.dart';
-import 'package:telnow_mobile_new/src/utils/provider.dart';
 
 import '../../api/api_auth_provider.dart';
-import '../../cubit/tab_history_cubit.dart';
+import '../../cubit/history_tab_cubit.dart';
 import '../../utils/U.dart';
 import '../../utils/extensions.dart';
 
@@ -42,9 +41,14 @@ class _RateMissionState extends State<RateMission> {
     controllerOptOther = TextEditingController();
 
     String locale = widget.parentContext.locale.toString();
-    aspectList = widget.list['_ratingAspect${locale[0].toUpperCase()}${locale[1]}'] != null &&
-        widget.list['_ratingAspect${locale[0].toUpperCase()}${locale[1]}'].trim() != ''
-        ? widget.list['_ratingAspect${locale[0].toUpperCase()}${locale[1]}'].split(';')
+    aspectList = widget.list[
+                    '_ratingAspect${locale[0].toUpperCase()}${locale[1]}'] !=
+                null &&
+            widget.list['_ratingAspect${locale[0].toUpperCase()}${locale[1]}']
+                    .trim() !=
+                ''
+        ? widget.list['_ratingAspect${locale[0].toUpperCase()}${locale[1]}']
+            .split(';')
         : [];
 
     if (aspectList.isNotEmpty) {
@@ -62,7 +66,6 @@ class _RateMissionState extends State<RateMission> {
 
   @override
   Widget build(BuildContext context) {
-    HistoryModule historyModule = Provider.of<HistoryModule>(context, listen: false);
     return AlertDialog(
       backgroundColor: Colors.transparent,
       contentPadding: EdgeInsets.zero,
@@ -81,15 +84,26 @@ class _RateMissionState extends State<RateMission> {
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: [
                   Text(widget.list[U.langColumn(context, 'requestSubject')],
-                      style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600)),
+                      style: const TextStyle(
+                          color: Colors.white,
+                          fontSize: 14,
+                          fontWeight: FontWeight.w600)),
                   const SizedBox(height: 4),
                   Row(
                     mainAxisAlignment: MainAxisAlignment.spaceBetween,
                     children: [
                       Text('${'ticketNumber'.tr()}: ${widget.list['ticketNo']}',
-                          style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w300)),
-                      Text(convertDate(widget.list['datetimeRequest'], context.locale.toString()),
-                          style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w300)),
+                          style: const TextStyle(
+                              color: Colors.white,
+                              fontSize: 12,
+                              fontWeight: FontWeight.w300)),
+                      Text(
+                          convertDate(widget.list['datetimeRequest'],
+                              context.locale.toString()),
+                          style: const TextStyle(
+                              color: Colors.white,
+                              fontSize: 12,
+                              fontWeight: FontWeight.w300)),
                     ],
                   ),
                 ],
@@ -99,10 +113,12 @@ class _RateMissionState extends State<RateMission> {
             // Bagian rating
             Container(
               padding: const EdgeInsets.fromLTRB(30, 15, 30, 15),
-              decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(25)),
+              decoration: BoxDecoration(
+                  color: Colors.white, borderRadius: BorderRadius.circular(25)),
               child: Column(
                 children: [
-                  Text('satisfactionAsk'.tr(), style: const TextStyle(fontSize: 16)),
+                  Text('satisfactionAsk'.tr(),
+                      style: const TextStyle(fontSize: 16)),
                   const SizedBox(height: 12),
                   Row(
                     mainAxisAlignment: MainAxisAlignment.center,
@@ -110,8 +126,12 @@ class _RateMissionState extends State<RateMission> {
                       return Expanded(
                         child: GestureDetector(
                           child: Image(
-                            image: AssetImage(widget.rating[i]['image'].toString()),
-                            color: Colors.white.withValues(alpha: tempRating == widget.rating[i]['key'] ? 1 : 0.3),
+                            image: AssetImage(
+                                widget.rating[i]['image'].toString()),
+                            color: Colors.white.withValues(
+                                alpha: tempRating == widget.rating[i]['key']
+                                    ? 1
+                                    : 0.3),
                             colorBlendMode: BlendMode.modulate,
                           ),
                           onTap: () {
@@ -126,7 +146,10 @@ class _RateMissionState extends State<RateMission> {
                   ),
                   const SizedBox(height: 6),
                   Text(description,
-                      style: const TextStyle(color: textColor, fontSize: 14, fontWeight: FontWeight.w300),
+                      style: const TextStyle(
+                          color: textColor,
+                          fontSize: 14,
+                          fontWeight: FontWeight.w300),
                       textAlign: TextAlign.center),
 
                   // Bagian aspek tambahan
@@ -137,7 +160,8 @@ class _RateMissionState extends State<RateMission> {
                     Column(
                       crossAxisAlignment: CrossAxisAlignment.start,
                       children: [
-                        Text('${'whatMakesYou'.tr()} $description?', style: const TextStyle(fontSize: 16)),
+                        Text('${'whatMakesYou'.tr()} $description?',
+                            style: const TextStyle(fontSize: 16)),
                         const SizedBox(height: 12),
                         Wrap(
                           runSpacing: 10,
@@ -145,21 +169,30 @@ class _RateMissionState extends State<RateMission> {
                           children: List.generate(aspectList.length, (i) {
                             return GestureDetector(
                               child: Container(
-                                padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
+                                padding: const EdgeInsets.symmetric(
+                                    vertical: 5, horizontal: 10),
                                 decoration: BoxDecoration(
                                   color: aspectList[i] == ratingAspect
                                       ? primaryColor.withValues(alpha: 0.3)
                                       : Colors.white,
                                   border: Border.all(
-                                      color: aspectList[i] == ratingAspect ? primaryColor : textColor),
-                                  borderRadius: const BorderRadius.all(Radius.circular(50)),
+                                      color: aspectList[i] == ratingAspect
+                                          ? primaryColor
+                                          : textColor),
+                                  borderRadius: const BorderRadius.all(
+                                      Radius.circular(50)),
                                 ),
-                                child: Text(aspectList[i] == 'OTHER_OPTION' ? 'letMeWrite'.tr() : aspectList[i],
+                                child: Text(
+                                    aspectList[i] == 'OTHER_OPTION'
+                                        ? 'letMeWrite'.tr()
+                                        : aspectList[i],
                                     style: const TextStyle(color: textColor)),
                               ),
                               onTap: () {
                                 setState(() {
-                                  ratingAspect = ratingAspect == aspectList[i] ? '' : aspectList[i];
+                                  ratingAspect = ratingAspect == aspectList[i]
+                                      ? ''
+                                      : aspectList[i];
                                 });
                               },
                             ).withHover();
@@ -171,25 +204,33 @@ class _RateMissionState extends State<RateMission> {
                             maxLength: 50,
                             autofocus: true,
                             controller: controllerOptOther,
-                            style: const TextStyle(fontSize: 14, color: Colors.black),
+                            style: const TextStyle(
+                                fontSize: 14, color: Colors.black),
                             decoration: InputDecoration(
                               counterText: '',
                               hintText: 'writeHere'.tr(),
-                              hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
+                              hintStyle: TextStyle(
+                                  color: textColor.withValues(alpha: 0.5),
+                                  fontSize: 14),
                               filled: true,
                               fillColor: Colors.white,
                               isDense: true,
                               contentPadding: const EdgeInsets.all(13),
                               prefixIcon: Padding(
-                                padding: const EdgeInsets.only(left: 13, right: 13),
-                                child: U.iconsax('edit-2', color: textColor, size: 22),
+                                padding:
+                                    const EdgeInsets.only(left: 13, right: 13),
+                                child: U.iconsax('edit-2',
+                                    color: textColor, size: 22),
                               ),
                               enabledBorder: OutlineInputBorder(
                                   borderRadius: BorderRadius.circular(12),
-                                  borderSide: BorderSide(color: const Color(0xff262626).withValues(alpha: 0.5))),
+                                  borderSide: BorderSide(
+                                      color: const Color(0xff262626)
+                                          .withValues(alpha: 0.5))),
                               focusedBorder: OutlineInputBorder(
                                   borderRadius: BorderRadius.circular(12),
-                                  borderSide: const BorderSide(color: primaryColor)),
+                                  borderSide:
+                                      const BorderSide(color: primaryColor)),
                             ),
                           ),
                       ],
@@ -232,10 +273,15 @@ class _RateMissionState extends State<RateMission> {
                                   data['aspect'] = controllerOptOther.text;
                                 }
 
-                                var res = await ApiAuthProvider()
-                                    .postData('/api/requestHistories/rateSatisfy', null, data);
+                                var res = await ApiAuthProvider().postData(
+                                    '/api/requestHistories/rateSatisfy',
+                                    null,
+                                    data);
                                 if (res != null) {
-                                  widget.parentContext.read<DoneCubit>().setSatisfaction(widget.index, tempRating);
+                                  widget.parentContext
+                                      .read<DoneCubit>()
+                                      .setSatisfaction(
+                                          widget.index, tempRating);
                                   navigateBack(context);
                                 }
                               }

+ 1 - 1
lib/src/layouts/components/responsive.dart

@@ -8,7 +8,7 @@ import 'package:telnow_mobile_new/src/layouts/web/menu_history.dart';
 import 'package:telnow_mobile_new/src/layouts/web/menu_home.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 
-import '../../cubit/menu_history_cubit.dart';
+import '../../cubit/history_menu_cubit.dart';
 
 @RoutePage()
 class AppResponsive extends StatelessWidget {

+ 21 - 18
lib/src/layouts/functions/request.dart

@@ -138,7 +138,7 @@ class RequestFunction{
   sendRequest(BuildContext context, widget, controllerNote, controllerReferenceNumber, controllerLocation, controllerDateString, currentSliderValue, dynamic asset) async{
     // showLoading(context, lottie: kIsWeb && !isCanvasKit ? null : 'Paperplane.json', text: 'sendingRequest'.tr());
     UIService.showLoading(text: 'sendingRequest'.tr(), lottie: kIsWeb && !isCanvasKit ? null : 'Paperplane.json' );
-    var data;
+    Map<String, dynamic> data;
     data = {
       'note': controllerNote.text.trim() == '' ? '' : controllerNote.text.trim(),
       'parentTicket': controllerReferenceNumber.text.trim() == '' ? null : controllerReferenceNumber.text.trim(),
@@ -152,27 +152,31 @@ class RequestFunction{
     if(!kIsWeb && !U.getInternetStatus()){
       closeLoading(context);
       sendRequestNoInternet(context, widget, controllerLocation, controllerNote, data: data, others: false);
-    }
-    else{
+    } else {
       List imageEncode = Provider.of<CreateSerModule>(context, listen: false).images();
       imageEncode.forEach((element) {
         element = base64Encode(element);
       });
       data['images'] = imageEncode;
 
-      var res = await apiAuthProvider.postData('/api/requestHistories/search/request/' + widget.request['type'] + '/' + widget.request['id'].toString() + '/' + widget.request['noteFormat'] + '/submit', null, data);
-      if (res != null) {
-        Future.delayed(Duration(seconds: 3), (){
-          closeLoading(context);
-          navigateTo(context, U.webView(context) ? WebReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch) : MobReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch)).then((res){
-            while (Navigator.canPop(context)){
-              Navigator.pop(context);
-            }
+      String url = '/api/requestHistories/search/request/${widget.request['type']}/${widget.request['id']}/${widget.request['noteFormat']}/submit';
+      try{
+        var res = await apiAuthProvider.postData(url, null, data);
+        if (res != null) {
+          Future.delayed(Duration(seconds: 3), (){
+            closeLoading(context);
+            navigateTo(context, U.webView(context) ? WebReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch) : MobReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch)).then((res){
+              while (Navigator.canPop(context)){
+                Navigator.pop(context);
+              }
+            });
           });
-        });
-      }
-      else{
-        closeLoading(context);
+        }
+        else{
+          closeLoading(context);
+        }
+      }catch(e){
+        UIService.showError(e.toString());
       }
     }
   }
@@ -206,8 +210,7 @@ class RequestFunction{
     if(!kIsWeb && !U.getInternetStatus()){
       closeLoading(context);
       sendRequestNoInternet(context, widget, controllerLocation, controllerNote, data: data, others: true);
-    }
-    else{
+    } else {
       List imageEncode = Provider.of<CreateSerModule>(context, listen: false).images();
       imageEncode.forEach((element) {
         element = base64Encode(element);
@@ -216,7 +219,7 @@ class RequestFunction{
 
       try{
         var res = await apiAuthProvider.postData('/api/receptionists/send/request', null, data);
-        print("res ##> $res");
+        // print("res ##> $res");
         if (res != null) {
           Future.delayed(Duration(seconds: 3), (){
             closeLoading(context);

+ 2 - 0
lib/src/layouts/mobile/history_detail.dart

@@ -15,6 +15,8 @@ import 'package:telnow_mobile_new/src/utils/ui_service.dart';
 import 'package:timelines_plus/timelines_plus.dart';
 import 'package:url_launcher/url_launcher.dart';
 
+import '../../cubit/pickup_asset_cubit.dart';
+
 // import '../functions/history.dart';
 
 class MobHistoryDetailPage extends StatefulWidget {

+ 0 - 1
lib/src/layouts/mobile/message_chat.dart

@@ -184,7 +184,6 @@ class _MobMessageChatPageState extends State<MobMessageChatPage> {
   }
 
   deleteCollection() {
-    debugPrint("popped");
     FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).get().then((value) {
       for (DocumentSnapshot ds in value.docs) {
         ds.reference.delete();

+ 1 - 82
lib/src/layouts/mobile/pickup_asset.dart

@@ -7,91 +7,10 @@ import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 
 import '../../api/api_auth_provider.dart';
+import '../../cubit/pickup_asset_cubit.dart';
 import '../../utils/extensions.dart';
 import '../components/widgets.dart';
 
-// State
-class AssetState extends Equatable {
-  final int idSelected;
-  final List<dynamic> data;
-  final List<dynamic> result;
-  final String keyword;
-  final int page;
-  final bool isLoading;
-
-  const AssetState({
-    this.idSelected = -1,
-    this.data = const [],
-    this.result = const [],
-    this.keyword = '',
-    this.page = 0,
-    this.isLoading = true,
-  });
-
-  AssetState copyWith({
-    int? idSelected,
-    List<dynamic>? data,
-    List<dynamic>? result,
-    String? keyword,
-    int? page,
-    bool? isLoading,
-  }) {
-    return AssetState(
-      idSelected: idSelected ?? this.idSelected,
-      data: data ?? this.data,
-      result: result ?? this.result,
-      keyword: keyword ?? this.keyword,
-      page: page ?? this.page,
-      isLoading: isLoading ?? this.isLoading,
-    );
-  }
-
-  @override
-  List<Object?> get props => [idSelected, data, result, keyword, page, isLoading];
-}
-
-// Cubit
-class AssetCubit extends Cubit<AssetState> {
-  AssetCubit() : super(const AssetState());
-
-  final ApiAuthProvider _apiAuthProvider = ApiAuthProvider();
-
-  void getData({String keyword = '', int page = 0}) async {
-    String filter = keyword == '' ? '{"f":["1","EQ","1"]}' : '{"or":[{"f":["name","LIKE","%$keyword%"]},{"f":["description","LIKE","%$keyword%"]}]}';
-    final params = {
-      "select": "id, code, name, description, image, status",
-      "sort": "name:ASC",
-      "filter": filter,
-      "size": "10",
-      "page": "$page"
-    };
-    final rawData = await _apiAuthProvider.getData('/api/assets/findData', params);
-    final data = rawData['_embedded']['assets'];
-
-    if (data.isEmpty && page != 0) return;
-    var finalData = page == 0 ? data : state.data + data;
-
-    emit(state.copyWith(data: finalData, keyword: keyword, page: page, isLoading: false));
-  }
-
-  void toggleSelection(int id) {
-    final newID = id;
-    final res = state.data.where((element) => element['id'] == newID).toList();
-
-    emit(state.copyWith(idSelected: newID, result: res));
-  }
-
-  void search(String keyword) {
-    getData(keyword: keyword, page: 0);
-
-    emit(state.copyWith(isLoading: true, data: []));
-  }
-
-  void loadNextPage() {
-    getData(keyword: state.keyword, page: state.page + 1);
-  }
-}
-
 class PickupAsset extends StatefulWidget {
   final bool web;
   const PickupAsset({super.key, required this.web});

+ 2 - 0
lib/src/layouts/mobile/request_create.dart

@@ -22,6 +22,8 @@ import 'package:telnow_mobile_new/src/utils/provider.dart';
 // import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
 import 'package:toggle_switch/toggle_switch.dart';
 
+import '../../cubit/pickup_asset_cubit.dart';
+
 class MobReqCreatePage extends StatefulWidget {
   final Map<String, dynamic> user;
   final Map<String, dynamic> request;

+ 692 - 1
lib/src/layouts/web/history_detail.dart

@@ -3,7 +3,11 @@ import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_linkify/flutter_linkify.dart';
+import 'package:open_file/open_file.dart';
 import 'package:provider/provider.dart';
+import 'package:telnow_mobile_new/src/cubit/history_detail_cubit.dart';
+import 'package:telnow_mobile_new/src/cubit/history_tab_cubit.dart';
+import 'package:telnow_mobile_new/src/cubit/user_data_cubit.dart';
 import 'package:telnow_mobile_new/src/layouts/functions/detail.dart';
 import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/layouts/web/history_forum.dart';
@@ -13,12 +17,698 @@ import 'package:telnow_mobile_new/src/utils/provider.dart';
 import 'package:timelines_plus/timelines_plus.dart';
 import 'package:url_launcher/url_launcher.dart';
 
+import '../../cubit/pickup_asset_cubit.dart';
 import '../mobile/pickup_asset.dart';
 
+class WebHistoryDetailPage extends StatelessWidget {
+  final int index;
+  final String locale;
+  const WebHistoryDetailPage({super.key, required this.index, required this.locale});
+
+  @override
+  Widget build(BuildContext context) {
+    var rating = [
+      {'key': 1, 'image': "assets/image/icon/very_dissatisfied.png", 'label': 'disatisfied'.tr()},
+      {'key': 2, 'image': "assets/image/icon/dissatisfied.png", 'label': 'lessSatisfied'.tr()},
+      {'key': 3, 'image': "assets/image/icon/neutral.png", 'label': 'satisfied'.tr()},
+      {'key': 4, 'image': "assets/image/icon/satisfied.png", 'label': 'verySatisfied'.tr()},
+      {'key': 5, 'image': "assets/image/icon/very_satisfied.png", 'label': 'reallyPleased'.tr()},
+    ];
+    TextEditingController controllerNote = TextEditingController();
+
+    return BlocConsumer<HistoryDetailCubit, HistoryDetailState>(
+      listener: (context, state) async {
+        if(state is OpenAttachment){
+          await OpenFile.open(state.fileName);
+        }
+
+        if(state is NavigateBack){
+          navigateBack(context, exc: state.exc);
+        }
+      },
+      builder: (context, state){
+        var list = state.data;
+        var user = context.read<UserCubit>().state.user;
+        HistoryDetailCubit historyDetailCubit = context.read<HistoryDetailCubit>();
+
+        return Scaffold(
+          backgroundColor: backgroundColor,
+          appBar: PreferredSize(preferredSize: Size.fromHeight(0), child: AppBar(elevation: 0, backgroundColor: primaryColor)),
+          body: Column(
+            children: [
+              Container(
+                padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100),
+                child: Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  children: [
+                    Text('detail'.tr(), style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis),
+                    GestureDetector(
+                      child: Text('buttonBack'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
+                      onTap: ()=>navigateBack(context),
+                    ).withHover(),
+                  ],
+                ),
+              ),
+              divider(),
+              if(state.isLoading)
+                Expanded(child: Center(child: loadingTemplateNoVoid(),))
+              else if(state.timeLimit)
+                Center(child: showButton(context),)
+              else if(list.isNotEmpty)
+                ...[Expanded(
+                child: SingleChildScrollView(
+                  padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100),
+                  child: Column(
+                    children: [
+                      Row(
+                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                        children: [
+                          Text(list[U.langColumn(context, 'requestGroupDescription')], style: TextStyle(color: textColor, fontSize: 16, fontWeight: FontWeight.w600)),
+                          list['currentState'] != 'DIPROSES' && list['currentState'] != 'DIANTRIKAN' && !list['autoResponse'] ? GestureDetector(
+                            child: Container(
+                              padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
+                              decoration: BoxDecoration(color: primaryColor.withValues(alpha: 0.1), border: Border.all(color: primaryColor), borderRadius: BorderRadius.all(Radius.circular(50))),
+                              child: Row(
+                                children: [
+                                  Text('forum'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
+                                  SizedBox(width: 5),
+                                  U.iconsax('messages-3', color: primaryColor, size: 20)
+                                ],
+                              ),
+                            ),
+                            onTap: (){
+                              Provider.of<HistoryModule>(context, listen: false).setForumFalse(index);
+                              navigateTo(context, WebHistoryForumPage(data: list, user: user));
+                            },
+                          ).withHover() : Container()
+                        ],
+                      ),
+                      SizedBox(height: 20),
+                      Row(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: [
+                          Expanded(
+                            child: Container(
+                              padding: EdgeInsets.all(20),
+                              decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))),
+                              child: Column(
+                                spacing: 16,
+                                crossAxisAlignment: CrossAxisAlignment.start,
+                                children: [
+                                  requestTiles(
+                                      image: list['_requestImage'] ?? "null",
+                                      title: list[U.langColumn(context, 'requestSubject')],
+                                      subtitle: list[U.langColumn(context, '_subjectDescription')] ?? '',
+                                      border: true, vertical: 0),
+                                  textHorizontal('ticketNumber'.tr(), list['ticketNo'], copy: true),
+                                  if(list['receptionistId'] != null) renderRequested(user, list),
+                                  textHorizontal('location'.tr(), list['ipphoneExtLocation']),
+                                  divider(opacity: 0.05),
+
+                                  list['autoResponse'] ? Column(
+                                    crossAxisAlignment: CrossAxisAlignment.start,
+                                    children: [
+                                      Text('note'.tr(), style: TextStyle(color: textColor)),
+                                      SizedBox(height: 5),
+                                      if(list['responseText'] != null && list['responseText'] != '') Linkify(
+                                        text: list['responseText'], style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.w300),
+                                        onOpen: (link) async {
+                                          if (await canLaunchUrl(Uri.parse(link.url))) {
+                                            await launchUrl(Uri.parse(link.url));
+                                          }
+                                        },
+                                      ),
+
+                                      list['responseAttachment']!=null&&list['responseAttachment']!=''?list['_isPdf']?GestureDetector(
+                                          child: Container(
+                                              width: double.infinity,
+                                              margin: EdgeInsets.only(top: 8),
+                                              decoration: BoxDecoration(border: Border.all(color: Colors.deepOrange), borderRadius: BorderRadius.all(Radius.circular(12))),
+                                              child: Center(
+                                                child: Column(
+                                                  children: [
+                                                    Icon(Icons.picture_as_pdf, color: Colors.deepOrange, size: 50),
+                                                    Text('seeAttachment'.tr(), style: TextStyle(color: Colors.black45, fontSize: 12))
+                                                  ],
+                                                ),
+                                              )
+                                          ),
+                                          onTap: () => historyDetailCubit.openAttachment(list)
+                                      ).withHover() : GestureDetector(
+                                          child: LayoutBuilder(
+                                            builder: (context, constraints) {
+                                              return Image.network(list['_mobileResponseAttachment'], fit: BoxFit.cover, width: double.infinity, height: constraints.maxWidth/(1.7), loadingBuilder:(BuildContext? context, Widget? child,ImageChunkEvent? loadingProgress) {
+                                                if (loadingProgress == null) return child!;
+                                                return SizedBox(
+                                                  height: constraints.maxWidth/(1.7),
+                                                  child: Center(
+                                                    child: CircularProgressIndicator(
+                                                      value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
+                                                    ),
+                                                  ),
+                                                );
+                                              });
+                                            },
+                                          ),
+                                          onTap: ()=>navigateTo(context, PhotoPreview('image'.tr(), list['_mobileResponseAttachment'], true))
+                                      ).withHover() : Container()
+                                    ],
+                                  ) : attachment_new(list),
+
+                                  list['autoResponse'] ? Container() : textVertical('note'.tr(), list['requestNote']!=''?list['requestNote']:'-'),
+                                  list['autoResponse'] || list['datetimeScheduled'] == null || list['datetimeScheduled'] == '' ? Container() : Text("${"scheduleMessage".tr()} ${list['datetimeScheduled']}."),
+
+                                  list['_parentTicket'] != null ? Column(
+                                    spacing: 16,
+                                    crossAxisAlignment: CrossAxisAlignment.start,
+                                    children: [
+                                      SizedBox(height: 16),
+                                      divider(opacity: 0.05),
+                                      Text('requestReference'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+                                      textHorizontal('ticketNumber'.tr(), list['_parentTicket']['ticketNo']),
+                                      divider(opacity: 0.05),
+                                      textHorizontal('subject'.tr(), list['_parentTicket'][U.langColumn(context, 'requestSubject')]),
+                                      textHorizontal('note'.tr(), list['_parentTicket']['requestNote']!=null && list['_parentTicket']['requestNote']!=''?list['_parentTicket']['requestNote']:'-'),
+                                      divider(opacity: 0.05),
+                                      list['_parentTicket']['_activeHoldRequest'] != null ? Row(
+                                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                                        children: [
+                                          Text('state'.tr(), style: TextStyle(fontSize: 14, color: textColor)),
+                                          Row(
+                                            mainAxisAlignment: MainAxisAlignment.end,
+                                            children: [
+                                              Image(image: AssetImage('assets/image/general/Watch.png'), width: 20, height: 20),
+                                              SizedBox(width: 5),
+                                              Text('hold'.tr(), style: TextStyle(fontSize: 14, color: primaryColor)),
+                                            ],
+                                          )
+                                        ],
+                                      ) : textHorizontal('state'.tr(), U.renderStatus(list['_parentTicket']['currentState'])),
+                                      textHorizontal('description'.tr(), list['_parentTicket'][U.langColumn(context, '_subjectDescription')]??''),
+                                    ],
+                                  ) : Container()
+                                ],
+                              ),
+                            ),
+                          ),
+                          SizedBox(width: 30),
+                          Expanded(
+                            child: Container(
+                              padding: EdgeInsets.all(20),
+                              decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))),
+                              child: list['autoResponse'] ? Container() : Column(
+                                spacing: 16,
+                                crossAxisAlignment: CrossAxisAlignment.start,
+                                children: [
+                                  if(state.asset.isEmpty && state.isActive) Row(
+                                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                                    children: [
+                                      Column(
+                                        spacing: 6,
+                                        crossAxisAlignment: CrossAxisAlignment.start,
+                                        children: [
+                                          Text('asset'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600),),
+                                          Text('noAssetAdded'.tr(), style: TextStyle(fontSize: 12),)
+                                        ],
+                                      ),
+                                      ElevatedButton.icon(
+                                        style: ElevatedButton.styleFrom(
+                                          backgroundColor: primaryColor,
+                                          foregroundColor: Colors.white,
+                                          shape: RoundedRectangleBorder(
+                                            borderRadius: BorderRadius.circular(24),
+                                          ),
+                                          elevation: 0.0,
+                                          alignment: Alignment.center,
+                                        ),
+                                        onPressed: () async{
+                                          final assetPicked = await Navigator.push(
+                                            context, MaterialPageRoute(
+                                            builder: (_) => BlocProvider(
+                                              create: (_) => AssetCubit()..getData(),
+                                              child: PickupAsset(web: true),
+                                            ),),
+                                          );
+
+                                          if (assetPicked != null) {
+                                            var data = assetPicked[0];
+                                            historyDetailCubit.setAsset(list['ticketNo'], data);
+                                          }
+                                        },
+                                        icon: Icon(Icons.add, size: 18,),
+                                        label: Text('addLabel'.tr(),),
+                                      ),
+                                    ],
+                                  ),
+                                  if(state.asset.isNotEmpty) Column(
+                                    spacing: 6,
+                                    crossAxisAlignment: CrossAxisAlignment.start,
+                                    children: [
+                                      Text('asset'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600,),),
+                                      Container(
+                                        decoration: BoxDecoration(
+                                          border: Border.all(color: Color(0xff262626).withValues(alpha: 0.1)),
+                                          borderRadius: BorderRadius.circular(12),
+                                        ),
+                                        child: Padding(
+                                          padding: EdgeInsets.symmetric(vertical: 0),
+                                          child: Row(
+                                            spacing: 12,
+                                            children: [
+                                              ClipRRect(
+                                                borderRadius: BorderRadius.circular(12.0),
+                                                child: Image.network(
+                                                  state.asset['_mobileImage'],
+                                                  width: 80,
+                                                  height: 64,
+                                                  fit: BoxFit.cover,
+                                                  errorBuilder: (context, error, stackTrace) =>
+                                                  const Icon(Icons.broken_image, size: 60, color: Colors.grey),
+                                                  loadingBuilder: (context, child, loadingProgress) {
+                                                    if (loadingProgress == null) return child;
+                                                    return const SizedBox(
+                                                      width: 80,
+                                                      height: 64,
+                                                      child: Center(child: CupertinoActivityIndicator()),
+                                                    );
+                                                  },
+                                                ),
+                                              ),
+                                              Expanded(
+                                                child: Column(
+                                                  crossAxisAlignment: CrossAxisAlignment.start,
+                                                  spacing: 4,
+                                                  children: [
+                                                    Text(state.asset['name'],
+                                                      maxLines: 1,
+                                                      overflow: TextOverflow.ellipsis,
+                                                      style: TextStyle(
+                                                        color: textColor,
+                                                        fontSize: 14,
+                                                        fontWeight: FontWeight.w600,
+                                                      ),),
+                                                    Text(state.asset['description'],
+                                                        maxLines: 2,
+                                                        overflow: TextOverflow.ellipsis,
+                                                        style: TextStyle(color: textColor, fontSize: 14)),
+                                                  ],
+                                                ),
+                                              ),
+                                              GestureDetector(
+                                                onTap: () async {
+                                                  var data = {'code': null};
+                                                  historyDetailCubit.setAsset(list['ticketNo'], data);
+                                                },
+                                                child: Padding(
+                                                  padding: const EdgeInsets.all(12.0),
+                                                  child: Icon(Icons.delete_rounded, size: 24, color: Colors.red.withValues(alpha: 0.85),),
+                                                ),
+                                              ).withHover()
+                                            ],
+                                          ),
+                                        ),
+                                      ),
+                                    ],
+                                  ),
+                                  if(state.asset.isNotEmpty) divider(opacity: 0.05),
+                                  Text('activity'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+                                  // SizedBox(height: 16),
+                                  list['currentState'] != 'DIBATALKAN' && list['currentState'] != 'DIANTRIKAN' && list['currentState'] != 'DIPROSES' && list['_collaboratorDataFilter'].length > 0?Column(
+                                    crossAxisAlignment: CrossAxisAlignment.start,
+                                    children: [
+                                      Text('servant'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                                      Container(
+                                        padding: EdgeInsets.only(top: 10, left: 16),
+                                        child: Column(
+                                          crossAxisAlignment: CrossAxisAlignment.start,
+                                          children: [
+                                            Text('1. ${list['servantNameStart']??'-'}', style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis),
+                                            Column(
+                                              crossAxisAlignment: CrossAxisAlignment.start,
+                                              children: List.generate(list['_collaboratorDataFilter'].length, (i) {
+                                                return Text('${i+2}. ${list['_collaboratorDataFilter'][i]['name']}', style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis);
+                                              }),
+                                            )
+                                          ],
+                                        ),
+                                      )
+                                    ],
+                                  ) : textHorizontal(
+                                      list['currentState'] == 'DIBATALKAN'?'canceledBy'.tr():'servant'.tr(),
+                                      list['currentState'] == 'DIBATALKAN'?list['servantNameCancel']??'-':list['currentState'] != 'DIANTRIKAN' && list['currentState'] != 'DIPROSES'?list['servantNameStart']??'-':'-'
+                                  ),
+                                  // SizedBox(height: 16),
+                                  divider(opacity: 0.05),
+                                  // SizedBox(height: 16),
+                                  Text('timeline'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                                  // SizedBox(height: 16),
+                                  Column(
+                                    crossAxisAlignment: CrossAxisAlignment.start,
+                                    children: [
+                                      timeline('stateRequested'.tr(), list['datetimeRequest'] != null ? convertDate(list['datetimeRequest'], context.locale.toString()) : '-', null, null, current: list['currentState'] == 'DIANTRIKAN' || list['currentState'] == 'DIPROSES', first: true),
+                                      list['currentState'] == 'DIMULAI' ? timeline('stateDone'.tr(), list['datetimeStart'] != null ? convertDate(list['datetimeStart'], context.locale.toString()) : '-', list['noteStart'], list['noteStartTranslate'], current: list['currentState'] == 'DIMULAI') : Container(),
+                                      !list['autoResponse'] && (list['currentState'] == 'DISELESAIKAN' || list['currentState'] == 'TUNTAS') ? timeline('startDoing'.tr(), list['datetimeStart'] != null ? convertDate(list['datetimeStart'], context.locale.toString()) : '-', list['noteStart'], list['noteStartTranslate']) : Container(),
+                                      !list['autoResponse'] && (list['currentState'] == 'DISELESAIKAN' || list['currentState'] == 'TUNTAS') ? timeline('stateFinish'.tr(), list['datetimeFinish'] != null ? convertDate(list['datetimeFinish'], context.locale.toString()) : '-', list['noteFinish'], list['noteFinishTranslate'], current: list['currentState'] == 'DISELESAIKAN' || list['currentState'] == 'TUNTAS') : Container(),
+                                      list['autoResponse'] && list['currentState'] == 'TUNTAS' ? timeline('stateFinish'.tr(), list['datetimeComplete'] != null ? convertDate(list['datetimeComplete'], context.locale.toString()) : '-', list['noteComplete'], list['noteCompleteTranslate'], current: list['currentState'] == 'TUNTAS') : Container(),
+                                      list['currentState'] == 'DIBATALKAN' ? timeline('stateCanceled'.tr(), list['datetimeCancel'] != null ? convertDate(list['datetimeCancel'], context.locale.toString()) : '-', list['noteCancel'], list['noteCancelTranslate'], current: list['currentState'] == 'DIBATALKAN') : Container(),
+                                    ],
+                                  ),
+
+                                  suspendPanel(context, list),
+                                  finishAttNew(list),
+
+                                  !list['autoResponse'] && (list['currentState'] == 'TUNTAS' || list['currentState'] == 'DISELESAIKAN') ? Column(
+                                    spacing: 16,
+                                    crossAxisAlignment: CrossAxisAlignment.start,
+                                    children: [
+                                      // SizedBox(height: 16),
+                                      divider(opacity: 0.05),
+                                      list['action'] != null && list['action'] != '' ? Column(
+                                        children: [
+                                          // SizedBox(height: 16),
+                                          textHorizontal('action'.tr(), list['action'])
+                                        ],
+                                      ) : Container(),
+                                      // SizedBox(height: 16),
+                                      Row(
+                                        children: [
+                                          Text('rate'.tr(), style: TextStyle(color: textColor.withValues(alpha: 0.75), fontSize: 14)),
+                                          Expanded(
+                                            child: list['satisfactionRate'] > 0 ? Row(
+                                              mainAxisAlignment: MainAxisAlignment.end,
+                                              children: [
+                                                Text(rating[list['satisfactionRate']-1]['label'].toString(), style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis),
+                                                SizedBox(width: 5),
+                                                Image(image: AssetImage(rating[list['satisfactionRate']-1]['image'].toString()), width: 25),
+                                              ],
+                                            ) : Text('unrated'.tr(), style: TextStyle(color: textColor, fontSize: 14), textAlign: TextAlign.end, overflow: TextOverflow.ellipsis),
+                                          ),
+                                        ],
+                                      ),
+                                      list['satisfactionRate'] > 0 ? Builder(
+                                        builder: (context) {
+                                          var aspect = '';
+                                          if(context.locale.toString() == 'id' && list['ratingAspectId'] != null){
+                                            aspect = list['ratingAspectId'];
+                                          } else if(context.locale.toString() == 'en' && list['ratingAspectEn'] != null){
+                                            aspect = list['ratingAspectEn'];
+                                          } else if(list['ratingAspectOri'] != null){
+                                            aspect = list['ratingAspectOri'];
+                                          }
+
+                                          return aspect.isNotEmpty ? Column(
+                                            children: [
+                                              // SizedBox(height: 16),
+                                              textHorizontal('ratingAspect'.tr(), aspect)
+                                            ],
+                                          ) : Container();
+                                        },
+                                      ) : Container(),
+                                    ],
+                                  ) : Container()
+                                ],
+                              ),
+                            ),
+                          )
+                        ],
+                      )
+                    ],
+                  ),
+                ),
+              ),
+              list.isEmpty || list['autoResponse'] || list['currentState'] == 'DISELESAIKAN' || list['currentState'] == 'TUNTAS' || list['currentState'] == 'DIBATALKAN' ? Container() : Container(
+                margin: EdgeInsets.symmetric(horizontal: 100),
+                padding: EdgeInsets.symmetric(vertical: 25),
+                decoration: BoxDecoration(border: Border(top: BorderSide(color: textColor.withValues(alpha: 0.05)))),
+                child: list['currentState'] == 'DIMULAI' && list['_canNotCancel'] ? Text('canNotCancel2'.tr(), style: TextStyle(color: textColor, fontSize: 14), textAlign: TextAlign.center) : Row(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: [
+                    Expanded(
+                      child: SizedBox(
+                        width: double.infinity,
+                        child: TextField(
+                          controller: controllerNote,
+                          style: const TextStyle(fontSize: 14, color: Colors.black),
+                          maxLength: 128,
+                          decoration: InputDecoration(
+                              counterText: '',
+                              counterStyle: TextStyle(color: Colors.transparent, fontSize: 0),
+                              hintText: 'addNoteCancel'.tr(),
+                              hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
+                              filled: true,
+                              fillColor: backgroundColor,
+                              hoverColor: Colors.black.withValues(alpha: 0.1),
+                              contentPadding: EdgeInsets.symmetric(vertical: 17, horizontal: 20),
+                              prefixIcon: Padding(padding: EdgeInsets.only(left: 20, right: 20), child: U.iconsax('edit-2', color: textColor, size: 24)),
+                              border: InputBorder.none,
+                              enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color(0xff262626).withValues(alpha: 0.5))),
+                              focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: primaryColor)),
+                              isDense: true
+                          ),
+                        ),
+                      ),
+                    ),
+                    SizedBox(width: 30),
+                    buttonTemplate(
+                      text: 'buttonCancel'.tr(),
+                      width: 200,
+                      height: 45,
+                      backgroundColor: textColor.withValues(alpha: 0.1),
+                      textColor: textColor.withValues(alpha: 0.75),
+                      borderColor: textColor.withValues(alpha: 0.20),
+                      action: (){
+                        var note = controllerNote.text.trim();
+                        if (note.length > 128){
+                          showError(context, 'noteLongAlert'.tr());
+                        } else {
+                          dialogConfirm(
+                            context: context,
+                            title: 'buttonCancel'.tr(),
+                            text: 'confirmCancel'.tr(),
+                            actionYes: () async {
+                              historyDetailCubit.cancelRequest(list, note);
+                            });
+                        }
+                      })
+                  ],
+                ),
+              )]
+            ],
+          ),
+        );
+      });
+  }
+
+  Widget renderRequested(user, list){
+    if(list['receptionistId'] != null){
+      if(user['roomAttendant'] != null && user['roomAttendant'] && user['userId'] != list['informantUserId'] && user['userId'] == list['receptionistId']){
+        return Column(
+          children: [
+            textHorizontal('requestedFor'.tr(), list['informantName']??'-'),
+            SizedBox(height: 16),
+          ],
+        );
+      }
+
+      if(user['userId'] == list['informantUserId'] && user['userId'] != list['receptionistId']){
+        return Column(
+          children: [
+            textHorizontal('requestedBy'.tr(), list['receptionistName']??'-'),
+            SizedBox(height: 16),
+          ],
+        );
+      }
+    }
+
+    return SizedBox();
+  }
+
+  Widget attachment_new(list){
+    List imageList = [];
+    if(list['_attachment'] != null){
+      for(var i = 1; i <= 5; i++){
+        if(list['_attachment']['_mobileRequestAtt$i'] != null){
+          imageList.add({'thumb': list['_attachment']['_mobileRequestThumb$i'], 'image': list['_attachment']['_mobileRequestAtt$i']});
+        }
+      }
+    }
+
+    return imageList.length > 0 ? Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Text('image'.tr(), style: TextStyle(color: textColor)),
+        SizedBox(height: 5),
+        LayoutBuilder(
+          builder: (context, constraints) {
+            var imageWidth = ((constraints.maxWidth-32)/5)-5;
+            return Row(
+              children: List.generate(imageList.length, (i){
+                return GestureDetector(
+                    child: Container(
+                      width: imageWidth, height: imageWidth, alignment: Alignment.topRight,
+                      margin: EdgeInsets.only(right: i == 4 ? 0 : 6),
+                      decoration: BoxDecoration(
+                          color: Colors.black12, borderRadius: BorderRadius.all(Radius.circular(5)), border: Border.all(color: Colors.black26, width: 0.5),
+                          image: imageList[i]['thumb'] != null ? DecorationImage(image: NetworkImage(imageList[i]['thumb']), fit: BoxFit.cover) : DecorationImage(image: AssetImage('assets/image/error/ImageNotFound.png'), fit: BoxFit.cover)
+                      ),
+                    ),
+                    onTap: ()=>navigateTo(context, PhotoPreviewGallery(title: 'image'.tr(), imageList: imageList, startIndex: i))
+                ).withHover();
+              }),
+            );
+          },
+        )
+      ],
+    ) : textVertical('image'.tr(), 'noImgAttach'.tr());
+  }
+
+  Widget timeline(label, text, note, noteTranslate, {bool first = false, bool current = false}) {
+    return TimelineTile(
+      nodeAlign: TimelineNodeAlign.start,
+      contents: Container(
+        margin: EdgeInsets.fromLTRB(16, 10, 16, 10),
+        child: Row(
+          children: [
+            Text(label, style: TextStyle(fontSize: 14, color: textColor.withValues(alpha: 0.85))),
+            SizedBox(width: 5),
+            Expanded(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.end,
+                children: [
+                  Text(text, style: TextStyle(fontSize: 13, color: textColor)),
+                  note != null && note != '' ? Text(note, style: TextStyle(fontSize: 12, color: primaryColor), textAlign: TextAlign.right) : Container(),
+                  U.autoTranslate() && noteTranslate != null && noteTranslate != '' && note != noteTranslate ? Container(
+                    margin: EdgeInsets.only(top: 1), decoration: BoxDecoration(border: Border(top: BorderSide(color: primaryColor.withValues(alpha: 0.3)))),
+                    child: Text('($noteTranslate)', style: TextStyle(fontSize: 12, color: primaryColor.withValues(alpha: 0.7), fontStyle: FontStyle.italic), textAlign: TextAlign.right),
+                  ) : Container(),
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
+      node: TimelineNode(
+        indicator: DotIndicator(color: current ? primaryColor : Color(0xffE8E8E8)),
+        startConnector: SolidLineConnector(color: first ? Colors.transparent : Color(0xffE8E8E8)),
+        endConnector: SolidLineConnector(color: current ? Colors.transparent : Color(0xffE8E8E8)),
+      ),
+    );
+  }
+
+  Widget suspendPanel(context, list){
+    var activeHold = list['_activeHoldRequest'];
+    var requestHold = list['_holdRequest'];
+
+    return Column(
+      children: [
+        activeHold != null ? Padding(padding: EdgeInsets.symmetric(vertical: 15), child: divider(opacity: 0.05)) : Container(),
+        activeHold != null ? Column(
+          children: [
+            Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                Text('state'.tr(), style: TextStyle(fontSize: 14, color: textColor)),
+                Container(
+                  child: Row(
+                    mainAxisAlignment: MainAxisAlignment.end,
+                    children: [
+                      Image(image: AssetImage('assets/image/general/Watch.png'), width: 20, height: 20),
+                      SizedBox(width: 5),
+                      Text('hold'.tr(), style: TextStyle(fontSize: 14, color: primaryColor)),
+                    ],
+                  ),
+                )
+              ],
+            ),
+            SizedBox(height: 16),
+            textHorizontal('description'.tr(), activeHold['description'])
+          ],
+        ) : Container(),
+
+        requestHold == null || requestHold.length == 0 || (requestHold.length == 1 && requestHold[0]['datetimeEnd'] == null) ? Container() : Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Padding(padding: EdgeInsets.symmetric(vertical: 15), child: divider(opacity: 0.05)),
+            Text('holdHistory'.tr(), style: TextStyle(fontSize: 14, color: Colors.black)),
+            SizedBox(height: 5),
+            Column(
+              children: List.generate(requestHold.length, (i) {
+                return requestHold[i]['datetimeEnd'] != null ? Container(
+                  padding: EdgeInsets.symmetric(vertical: 5),
+                  child: Row(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      Text('${i+1}.', style: TextStyle(fontSize: 14, color: Colors.black)),
+                      SizedBox(width: 5),
+                      Expanded(
+                        child: Column(
+                          crossAxisAlignment: CrossAxisAlignment.start,
+                          children: [
+                            Text(requestHold[i]['description'], style: TextStyle(fontSize: 14, color: Colors.black), textAlign: TextAlign.start),
+                            SizedBox(height: 3),
+                            Text('${DateFormat('dd MMM yyyy, HH:mm', context.locale.toString()).format(DateTime.parse(requestHold[i]['datetimeStart']))} - ${DateFormat('dd MMM yyyy, HH:mm', context.locale.toString()).format(DateTime.parse(requestHold[i]['datetimeEnd']))}', style: TextStyle(fontSize: 14, color: primaryColor))
+                          ],
+                        ),
+                      )
+                    ],
+                  ),
+                ) : Container();
+              }),
+            )
+          ],
+        )
+      ],
+    );
+  }
+
+  Widget finishAttNew(list){
+    List imageList = [];
+
+    if((list['currentState'] == 'DISELESAIKAN' || list['currentState'] == 'TUNTAS')){
+      if(list['_attachment'] != null){
+        for(var i = 1; i <= 5; i++){
+          if(list['_attachment']['_mobileFinishAtt$i'] != null){
+            imageList.add({'thumb': list['_attachment']['_mobileFinishThumb$i'], 'image': list['_attachment']['_mobileFinishAtt$i']});
+          }
+        }
+      }
+    }
+
+    return imageList.isNotEmpty ? Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        SizedBox(height: 16),
+        Text('finishAttachment'.tr(), style: TextStyle(color: textColor)),
+        SizedBox(height: 8),
+        LayoutBuilder(
+          builder: (context, constraints) {
+            var imageWidth = ((constraints.maxWidth-32)/5)-5;
+            return Row(
+              children: List.generate(imageList.length, (i){
+                return GestureDetector(
+                    child: Container(
+                      width: imageWidth, height: imageWidth, alignment: Alignment.topRight,
+                      margin: EdgeInsets.only(right: i == 4 ? 0 : 6),
+                      decoration: BoxDecoration(
+                          color: Colors.black12, borderRadius: BorderRadius.all(Radius.circular(5)), border: Border.all(color: Colors.black26, width: 0.5),
+                          image: imageList[i]['thumb'] != null ? DecorationImage(image: NetworkImage(imageList[i]['thumb']), fit: BoxFit.cover) : DecorationImage(image: AssetImage('assets/image/error/ImageNotFound.png'), fit: BoxFit.cover)
+                      ),
+                    ),
+                    onTap: ()=>navigateTo(context, PhotoPreviewGallery(title: 'finishAttachment'.tr(), imageList: imageList, startIndex: i))
+                ).withHover();
+              }),
+            );
+          },
+        )
+      ],
+    ) : Container();
+  }
+}
+/**
 class WebHistoryDetailPage extends StatefulWidget {
   final int index;
   final String locale;
-  const WebHistoryDetailPage({required this.index, super.key, required this.locale});
+  const WebHistoryDetailPage({required this.index, super.key, required this.locale, required TabState state});
 
   @override
   State<WebHistoryDetailPage> createState() => _WebHistoryDetailPageState();
@@ -729,3 +1419,4 @@ class _WebHistoryDetailPageState extends State<WebHistoryDetailPage> {
     );
   }
 }
+**/

+ 2 - 2
lib/src/layouts/web/menu-history/build_filter.dart

@@ -1,8 +1,8 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../../cubit/menu_history_cubit.dart';
-import '../../../cubit/tab_history_cubit.dart';
+import '../../../cubit/history_menu_cubit.dart';
+import '../../../cubit/history_tab_cubit.dart';
 import '../../../utils/U.dart';
 import '../../../utils/extensions.dart';
 

+ 42 - 35
lib/src/layouts/web/menu-history/build_tab.dart

@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../../cubit/menu_history_cubit.dart';
+import '../../../cubit/history_menu_cubit.dart';
 import '../../../utils/U.dart';
 import '../../../utils/extensions.dart';
 
@@ -14,50 +14,57 @@ class BuildTab extends StatelessWidget {
   Widget build(BuildContext context) {
     return BlocBuilder<MenuHistoryCubit, MenuHistoryState>(
         builder: (context, state) {
-          return Row(
-            children: [
-              GestureDetector(
-                child: Container(
-                  margin: EdgeInsets.only(left: 16),
-                  padding: EdgeInsets.symmetric(vertical: 5, horizontal: 16),
-                  decoration: BoxDecoration(
-                      color: Colors.white,
-                      border: Border(bottom: BorderSide(
+      return Row(
+        children: [
+          GestureDetector(
+            child: Container(
+              margin: EdgeInsets.only(left: 16),
+              padding: EdgeInsets.symmetric(vertical: 5, horizontal: 16),
+              decoration: BoxDecoration(
+                  color: Colors.white,
+                  border: Border(
+                      bottom: BorderSide(
                           color: state.activeTab.name == "ongoing"
                               ? primaryColor
                               : primaryColor.withValues(alpha: 0.3),
                           width: state.activeTab.name == "ongoing" ? 2 : 1))),
-                  child: Text('ongoing'.tr(), style: TextStyle(
+              child: Text('ongoing'.tr(),
+                  style: TextStyle(
                       color: state.activeTab.name == "ongoing"
                           ? textColor
-                          : textColor.withValues(alpha: 0.65), fontSize: 16)),
-                ),
-                onTap: () {
-                  controller.animateTo(0);
-                  context.read<MenuHistoryCubit>().setActiveTab(HistoryTab.ongoing);
-                },
-              ).withHover(),
-              GestureDetector(
-                child: Container(
-                  padding: EdgeInsets.symmetric(vertical: 5, horizontal: 16),
-                  decoration: BoxDecoration(color: Colors.white,
-                      border: Border(bottom: BorderSide(
+                          : textColor.withValues(alpha: 0.65),
+                      fontSize: 16)),
+            ),
+            onTap: () {
+              controller.animateTo(0);
+              context.read<MenuHistoryCubit>().setActiveTab(HistoryTab.ongoing);
+            },
+          ).withHover(),
+          GestureDetector(
+            child: Container(
+              padding: EdgeInsets.symmetric(vertical: 5, horizontal: 16),
+              decoration: BoxDecoration(
+                  color: Colors.white,
+                  border: Border(
+                      bottom: BorderSide(
                           color: state.activeTab.name == "done"
                               ? primaryColor
                               : primaryColor.withValues(alpha: 0.3),
                           width: state.activeTab.name == "done" ? 2 : 1))),
-                  child: Text('done'.tr(), style: TextStyle(
+              child: Text('done'.tr(),
+                  style: TextStyle(
                       color: state.activeTab.name == "done"
                           ? textColor
-                          : textColor.withValues(alpha: 0.65), fontSize: 16)),
-                ),
-                onTap: () {
-                  controller.animateTo(1);
-                  context.read<MenuHistoryCubit>().setActiveTab(HistoryTab.done);
-                },
-              ).withHover(),
-            ],
-          );;
-        });
+                          : textColor.withValues(alpha: 0.65),
+                      fontSize: 16)),
+            ),
+            onTap: () {
+              controller.animateTo(1);
+              context.read<MenuHistoryCubit>().setActiveTab(HistoryTab.done);
+            },
+          ).withHover(),
+        ],
+      );
+    });
   }
-}
+}

+ 48 - 32
lib/src/layouts/web/menu-history/done_container.dart

@@ -1,19 +1,14 @@
 import 'package:easy_localization/easy_localization.dart';
+import 'package:easy_refresh/easy_refresh.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
 import 'package:provider/provider.dart';
 import 'package:telnow_mobile_new/src/layouts/web/menu-history/history_item_widget.dart';
-
-import '../../../cubit/menu_history_cubit.dart';
-import '../../../cubit/tab_history_cubit.dart';
+import '../../../cubit/history_tab_cubit.dart';
 import '../../../utils/U.dart';
-import '../../../utils/extensions.dart';
 import '../../../utils/provider.dart';
-import '../../components/rate_mission.dart';
 import '../../components/template.dart';
-import '../../functions/history.dart' hide HistoryTab;
-import '../history_detail.dart';
 
 class DoneContainer extends StatefulWidget {
   const DoneContainer({super.key});
@@ -47,15 +42,33 @@ class _DoneContainerState extends State<DoneContainer>
   @override
   Widget build(BuildContext context) {
     super.build(context);
-    HistoryModule historyModule = Provider.of<HistoryModule>(context, listen: false);
-    HistoryFunction historyFunction = HistoryFunction();
 
     List<dynamic> rating = [
-      {'key': 1, 'image': "assets/image/icon/very_dissatisfied.png", 'label': 'disatisfied'.tr()},
-      {'key': 2, 'image': "assets/image/icon/dissatisfied.png", 'label': 'lessSatisfied'.tr()},
-      {'key': 3, 'image': "assets/image/icon/neutral.png", 'label': 'satisfied'.tr()},
-      {'key': 4, 'image': "assets/image/icon/satisfied.png", 'label': 'verySatisfied'.tr()},
-      {'key': 5, 'image': "assets/image/icon/very_satisfied.png", 'label': 'reallyPleased'.tr()},
+      {
+        'key': 1,
+        'image': "assets/image/icon/very_dissatisfied.png",
+        'label': 'disatisfied'.tr()
+      },
+      {
+        'key': 2,
+        'image': "assets/image/icon/dissatisfied.png",
+        'label': 'lessSatisfied'.tr()
+      },
+      {
+        'key': 3,
+        'image': "assets/image/icon/neutral.png",
+        'label': 'satisfied'.tr()
+      },
+      {
+        'key': 4,
+        'image': "assets/image/icon/satisfied.png",
+        'label': 'verySatisfied'.tr()
+      },
+      {
+        'key': 5,
+        'image': "assets/image/icon/very_satisfied.png",
+        'label': 'reallyPleased'.tr()
+      },
     ];
 
     return BlocBuilder<DoneCubit, TabState>(
@@ -63,28 +76,31 @@ class _DoneContainerState extends State<DoneContainer>
         UserModule userModule = Provider.of<UserModule>(context, listen: false);
         var data = state.data;
 
-        if(state.isLoading) {
+        if (state.isLoading) {
           return loadingTemplateNoVoid();
-        } else if(data.isEmpty){
+        } else if (data.isEmpty) {
           return NoDataPage();
         }
 
-        return LazyLoadScrollView(
-          onEndOfPage: () => context.read<DoneCubit>().loadNextPage(),
-          scrollOffset: 300,
-          child: ListView.builder(
-            itemCount: data.length,
-            itemBuilder: (context, i){
-              return HistoryItemWidget(
-                tab: 'done',
-                state: state,
-                index: i,
-                item: data[i],
-                userModule: userModule,
-                rating: rating,
-                animationController: _animationController,
-              );
-            }
+        return EasyRefresh(
+          header: MaterialHeader(clamping: true, color: primaryColor),
+          onRefresh: () => context.read<DoneCubit>().reloadPage(),
+          child: LazyLoadScrollView(
+            onEndOfPage: () => context.read<DoneCubit>().loadNextPage(),
+            scrollOffset: 300,
+            child: ListView.builder(
+                itemCount: data.length,
+                itemBuilder: (context, i) {
+                  return HistoryItemWidget(
+                    tab: 'done',
+                    state: state,
+                    index: i,
+                    item: data[i],
+                    userModule: userModule,
+                    rating: rating,
+                    animationController: _animationController,
+                  );
+                }),
           ),
         );
       },

+ 43 - 7
lib/src/layouts/web/menu-history/history_item_widget.dart

@@ -1,15 +1,19 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-import 'package:telnow_mobile_new/src/cubit/menu_history_cubit.dart' hide HistoryTab;
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:telnow_mobile_new/src/cubit/history_detail_cubit.dart';
+import 'package:telnow_mobile_new/src/cubit/history_menu_cubit.dart' hide HistoryTab;
 
-import '../../../cubit/tab_history_cubit.dart';
+import '../../../cubit/history_tab_cubit.dart';
+import '../../../cubit/user_data_cubit.dart';
+import '../../../repository/history_repository.dart';
 import '../../../utils/U.dart';
 import '../../../utils/extensions.dart';
 import '../../../utils/provider.dart';
 import '../../components/rate_mission.dart';
 import '../../components/template.dart';
 import '../history_detail.dart';
+import '../request_create.dart';
 
 class HistoryItemWidget extends StatelessWidget {
   final Map<String, dynamic> item;
@@ -40,9 +44,12 @@ class HistoryItemWidget extends StatelessWidget {
         } else {
           navigateTo(
             context,
-            WebHistoryDetailPage(
-              index: index,
-              locale: context.locale.toString(),
+            BlocProvider<HistoryDetailCubit>(
+              create: (_) => HistoryDetailCubit()..getData(item['ticketNo'], context.locale.toString()),
+              child: WebHistoryDetailPage(
+                index: index,
+                locale: context.locale.toString(),
+              ),
             ),
           );
         }
@@ -186,7 +193,36 @@ class HistoryItemWidget extends StatelessWidget {
           child: Text('reqAgain'.tr(),
               style: const TextStyle(color: primaryColor, fontSize: 14)),
         ),
-        onTap: () => context.read<MenuHistoryCubit>().requestAgain(item) //historyFunction.requestAgainAction(index),
+        // onTap: () => navigateTo(context,
+        //     WebReqCreatePage(user: {}, request: {})
+        // )
+        onTap: () async{
+          var menuHistoryCubit = context.read<MenuHistoryCubit>();
+          Map<String, dynamic> user = context.read<UserCubit>().state.user;
+
+          menuHistoryCubit.setMultiSelectMode(false);
+
+          try {
+            String tenant = '';
+            String filter = '';
+
+            if ((item['requestGroupCode']).split(" ").length != 1) {
+              tenant = ',{"f":["tenantCode","EQ","${(item['requestGroupCode']).split(" ").first}"]}';
+            }
+            filter = '{"and":[{"f":["code","EQ","${item['requestCode']}"]}$tenant]}';
+
+            var res = await HistoryRepository().checkRequest(filter);
+            if (res.isNotEmpty) {
+              if (res.containsKey('_embedded')) {
+                navigateTo(context, WebReqCreatePage(user: user, request: res['_embedded']['requests'][0],));
+              } else {
+                menuHistoryCubit.showError('reqCodeNotFound'.tr());
+              }
+            }
+          } catch (e) {
+            menuHistoryCubit.showError('reqCodeNotFound'.tr());
+          }
+        }
       ).withHover()
     ];
 

+ 42 - 43
lib/src/layouts/web/menu-history/ongoing_container.dart

@@ -1,3 +1,4 @@
+import 'package:easy_refresh/easy_refresh.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
@@ -5,11 +6,10 @@ import 'package:provider/provider.dart';
 import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/layouts/web/menu-history/history_item_widget.dart';
 
-import '../../../cubit/menu_history_cubit.dart';
-import '../../../cubit/tab_history_cubit.dart';
+import '../../../cubit/history_menu_cubit.dart';
+import '../../../cubit/history_tab_cubit.dart';
 import '../../../utils/U.dart';
 import '../../../utils/provider.dart';
-import '../../functions/history.dart' hide HistoryTab;
 
 class OngoingContainer extends StatefulWidget {
   const OngoingContainer({super.key});
@@ -22,7 +22,6 @@ class _OngoingContainerState extends State<OngoingContainer>
     with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
   late AnimationController _animationController;
 
-
   @override
   bool get wantKeepAlive => true;
 
@@ -46,57 +45,57 @@ class _OngoingContainerState extends State<OngoingContainer>
   Widget build(BuildContext context) {
     super.build(context);
     return BlocBuilder<OngoingCubit, TabState>(
-      builder: (BuildContext context, state) {
-        UserModule userModule = Provider.of<UserModule>(context, listen: false);
-        HistoryFunction historyFunction = HistoryFunction();
-        var pendingData = context.read<MenuHistoryCubit>().state.pendingData;
-        List<dynamic> rating = [];
+        builder: (BuildContext context, state) {
+      UserModule userModule = Provider.of<UserModule>(context, listen: false);
+      var pendingData = context.read<MenuHistoryCubit>().state.pendingData;
+      List<dynamic> rating = [];
 
-        if(state.isLoading) {
-          return loadingTemplateNoVoid();
-        }
+      if (state.isLoading) {
+        return loadingTemplateNoVoid();
+      }
 
-        if(!U.getInternetStatus() && pendingData.isNotEmpty) {
-          return ListView.builder(
+      if (!U.getInternetStatus() && pendingData.isNotEmpty) {
+        return ListView.builder(
             itemCount: state.data.length,
             itemBuilder: (context, i) {
               return HistoryItemWidget(
-                tab: 'ongoing',
-                state: state,
-                index: i,
-                item: pendingData[i],
-                userModule: userModule,
-                rating: rating,
-                animationController: _animationController
-              );
+                  tab: 'ongoing',
+                  state: state,
+                  index: i,
+                  item: pendingData[i],
+                  userModule: userModule,
+                  rating: rating,
+                  animationController: _animationController);
             });
-        }
+      }
 
-        if(state.data.isEmpty) {
-          return NoDataPage();
-        }
+      if (state.data.isEmpty) {
+        return NoDataPage();
+      }
 
-        return LazyLoadScrollView(
+      return EasyRefresh(
+        header: MaterialHeader(clamping: true, color: primaryColor),
+        onRefresh: () => context.read<OngoingCubit>().reloadPage(),
+        child: LazyLoadScrollView(
           onEndOfPage: () => context.read<OngoingCubit>().loadNextPage(),
           scrollOffset: 300,
           child: ListView.builder(
-            itemCount: state.data.length,
-            itemBuilder: (context, i) {
-              var dataRequest = state.data;
+              itemCount: state.data.length,
+              itemBuilder: (context, i) {
+                var dataRequest = state.data;
 
-              return HistoryItemWidget(
-                tab: 'ongoing',
-                state: state,
-                index: i,
-                item: dataRequest[i],
-                userModule: userModule,
-                rating: rating,
-                animationController: _animationController
-              );
-            }),
-        );
-      }
-    );
+                return HistoryItemWidget(
+                    tab: 'ongoing',
+                    state: state,
+                    index: i,
+                    item: dataRequest[i],
+                    userModule: userModule,
+                    rating: rating,
+                    animationController: _animationController);
+              }),
+        ),
+      );
+    });
   }
 
   // renderStatus(text, color){

+ 11 - 4
lib/src/layouts/web/menu_history.dart

@@ -2,14 +2,14 @@ import 'package:auto_route/auto_route.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:telnow_mobile_new/src/cubit/menu_history_cubit.dart';
+import 'package:telnow_mobile_new/src/cubit/history_menu_cubit.dart';
 import 'package:telnow_mobile_new/src/cubit/user_data_cubit.dart';
 import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/layouts/web/request_create.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/dio_logging_interceptors.dart';
 
-import '../../cubit/tab_history_cubit.dart';
+import '../../cubit/history_tab_cubit.dart';
 import '../../utils/extensions.dart';
 import '../../utils/ui_service.dart';
 import '../mobile/request_create.dart';
@@ -42,7 +42,11 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
   void initState() {
     _animationController = AnimationController(vsync: this, duration: Duration(seconds: 1));
     _animationController.repeat(reverse: true);
-    _tabController = TabController(length: 2, vsync: this);
+    _tabController = TabController(
+      length: 2,
+      vsync: this,
+      initialIndex: context.read<MenuHistoryCubit>().state.activeIndex,
+    );
 
     WidgetsBinding.instance.addPostFrameCallback((_) {
       context.read<UserCubit>().getUser();
@@ -99,6 +103,9 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
             backgroundColor: backgroundColor,
             body: BlocConsumer<MenuHistoryCubit, MenuHistoryState>(
               listener: (context, state) {
+                if(state is TabChanged){
+                  _tabController.index = state.index;
+                }
                 if(state is ReloadPage){
                   context.read<MenuHistoryCubit>().setActiveTab(HistoryTab.done);
                   context.read<DoneCubit>().reloadPage();
@@ -106,7 +113,7 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
                 if(state is CreateNewRequest){
                   var user = context.read<UserCubit>().state.user;
                   var request = state.request;
-                  UIService.navigateTo(
+                  navigateTo(context,
                       U.webView(context) ?
                       WebReqCreatePage(user: user, request: request) :
                       MobReqCreatePage(user: user, request: request)

+ 1 - 0
lib/src/layouts/web/request_create.dart

@@ -16,6 +16,7 @@ import 'package:telnow_mobile_new/src/utils/extensions.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
 import 'package:toggle_switch/toggle_switch.dart';
 
+import '../../cubit/pickup_asset_cubit.dart';
 import '../mobile/pickup_asset.dart';
 
 class WebReqCreatePage extends StatefulWidget {

+ 1 - 1
lib/src/layouts/web/request_success.dart

@@ -59,7 +59,7 @@ class _WebReqSuccessPageState extends State<WebReqSuccessPage> {
 
   backAction(){
     int count = 0;
-    int page = widget.fromSearch ? 3 : 2;
+    int page = widget.fromSearch ? 3 : 1;
     Navigator.of(context).popUntil((_) => count++ >= page);
   }
 

+ 164 - 10
lib/src/repository/history_repository.dart

@@ -1,29 +1,45 @@
+import 'dart:io';
+
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:telnow_mobile_new/src/api/jwt_token.dart';
+import 'package:translator/translator.dart';
 
 import '../api/api_auth_provider.dart';
+import '../utils/U.dart';
 import '../utils/cache_manager.dart';
 
 class HistoryRepository {
   final ApiAuthProvider _apiAuthProvider = ApiAuthProvider();
+  final translator = GoogleTranslator();
+  final JwtToken token = JwtToken();
 
   //## cuma request doang, balikin hasil request ke cubit
-  Future<List<dynamic>> getData(String key, String filter, int page, int size, List<dynamic> activeForum) async {
-    try{
+  Future<List<dynamic>> getData(String key, String filter, int page, int size,
+      List<dynamic> activeForum) async {
+    try {
       String url = '/api/requestHistories/search/myReqHistory';
       var sort = ['datetimeRequest,desc'];
 
-      var mission = await _apiAuthProvider.getData(url, {'size': size.toString(), 'page': page.toString(), 'sort': sort, 'filter': filter});
+      var mission = await _apiAuthProvider.getData(url, {
+        'size': size.toString(),
+        'page': page.toString(),
+        'sort': sort,
+        'filter': filter
+      });
       List tempData = [];
 
-      if (mission != null){
+      if (mission != null) {
         if (mission.containsKey('_embedded')) {
-          for (int i = 0; i < mission['_embedded']['requestHistories'].length; i++) {
+          for (int i = 0; i <
+              mission['_embedded']['requestHistories'].length; i++) {
             var isFrm = false;
             String id = "";
             String msg = "";
 
             activeForum.forEach((el) {
-              if (el['ticketId'] == mission['_embedded']['requestHistories'][i]['ticketNo']) {
+              if (el['ticketId'] ==
+                  mission['_embedded']['requestHistories'][i]['ticketNo']) {
                 id = el['id'];
                 msg = el['desc'];
               }
@@ -43,15 +59,17 @@ class HistoryRepository {
       CacheMan.writeData(key, tempData);
 
       return tempData;
-    } catch(e){
+    } catch (e) {
       debugPrint(e.toString());
     }
 
     return [];
   }
 
-  deleteData(List<String> params) async{
-    var res = await _apiAuthProvider.postData("/api/requestHistories/deleteMyHistory", {"ticketNumbers": params}, null);
+  deleteData(List<String> params) async {
+    var res = await _apiAuthProvider.postData(
+        "/api/requestHistories/deleteMyHistory", {"ticketNumbers": params},
+        null);
 
     if (res != null) {
       return res;
@@ -81,9 +99,145 @@ class HistoryRepository {
       var res = await _apiAuthProvider.getData(
           '/api/requests/search/customFind', {'filter': filter});
       return res;
-    } catch(e){
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    return {};
+  }
+
+  Future<Map<String, dynamic>> getDetail(String ticketNo, String locale) async {
+    try {
+      var data = await getByTicketNo(ticketNo);
+
+      if (data['datetimeScheduled'] != null &&
+          data['datetimeScheduled'] != '' && data['noteFormat'] == 'DATE') {
+        var date = data['datetimeScheduled'];
+        data['datetimeScheduled'] =
+            DateFormat('dd MMM yyyy HH:mm', 'id').format(DateTime.parse(date));
+      }
+
+      data['noteStartTranslate'] = '';
+      data['noteFinishTranslate'] = '';
+      data['noteCompleteTranslate'] = '';
+      data['noteCancelTranslate'] = '';
+
+      if (locale == 'zh') locale = 'zh-cn';
+
+      if (data['noteStart'] == 'transferNoteBySystem') {
+        data['noteStart'] = 'transferNoteBySystem'.tr();
+      } else if (data['noteStart'] == 'escalationNoteBySystem') {
+        data['noteStart'] = 'escalationNoteBySystem'.tr();
+      } else {
+        if (U.autoTranslate() && data['noteStart'] != null &&
+            data['noteStart'] != '') {
+          var translate = await translator.translate(
+              data['noteStart'], to: locale);
+          data['noteStartTranslate'] = translate.text;
+        }
+      }
+
+      if (U.autoTranslate() && data['noteFinish'] != null &&
+          data['noteFinish'] != '') {
+        var translate = await translator.translate(
+            data['noteFinish'], to: locale);
+        data['noteFinishTranslate'] = translate.text;
+      }
+      if (U.autoTranslate() && data['noteComplete'] != null &&
+          data['noteComplete'] != '') {
+        var translate = await translator.translate(
+            data['noteComplete'], to: locale);
+        data['noteCompleteTranslate'] = translate.text;
+      }
+      if (U.autoTranslate() && data['noteCancel'] != null &&
+          data['noteCancel'] != '') {
+        var translate = await translator.translate(
+            data['noteCancel'], to: locale);
+        data['noteCancelTranslate'] = translate.text;
+      }
+      data['_collaboratorDataFilter'] = data['_collaboratorData']
+          .where((el) => el['status'] == 'ACCEPTED')
+          .toList();
+
+      return data;
+      //   {
+      //   'list': data
+      // };
+    } catch (e) {
       debugPrint(e.toString());
     }
     return {};
   }
+
+  Future<dynamic> getByTicketNo(String ticketNo) async {
+    Map<String, dynamic> res = {};
+    try {
+      var data = await _apiAuthProvider.getData(
+          '/api/requestHistories/$ticketNo', null);
+
+      if (data != null) {
+        res = data;
+      }
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    return res;
+  }
+
+  getFileName(pdfUrl){
+    var imgSplit = pdfUrl.toString().split('/');
+    return imgSplit[imgSplit.length-1];
+  }
+
+  Future<dynamic> getPath(Map<String, dynamic> data) async {
+    String path = await token.getPath();
+    var filePath = '$path/TelNow/Files/';
+
+    if(!File(filePath + getFileName(data['responseAttachment'])).existsSync()){
+      await _apiAuthProvider.downloadImage(data['responseAttachment'], filePath + getFileName(data['responseAttachment']));
+    }
+
+    return filePath + getFileName(data['responseAttachment']);
+  }
+
+  Future<Map<String, dynamic>> setAsset(String ticketNo, Map<String, dynamic> data, String baseUrl) async {
+    Map<String, dynamic> asset = {};
+
+    var params = {'assetCode': data['code']};
+    try{
+      var res = await _apiAuthProvider.postData('/api/requestHistories/$ticketNo/asset/add', null, params);
+
+      if (res != null){
+        if (data['code'] != null){
+          asset = data;
+        }
+      }
+
+      return asset;
+    }catch(e){
+      return asset;
+    }
+
+  }
+
+  Future<dynamic> cancelRequest(list, Map<String, dynamic> data) async {
+    var res = {};
+
+    try{
+      res = await _apiAuthProvider.postData('/api/requestHistories/search/request/cancel/${list['ticketNo']}', null, data);
+    }catch(e){}
+
+    return res;
+  }
+
+
+  Future<String> getBaseUrl() async {
+    try {
+      return '${U.decodeBase64Url(U.getBaseUrl()!)}${U.decodeBase64Url(
+          U.getAccessCode()!)}';
+    }catch(e) {
+      debugPrint(e.toString());
+    }
+    return '';
+  }
+
 }

+ 1 - 1
lib/src/utils/dio_logging_interceptors.dart

@@ -63,7 +63,7 @@ class DioLoggingInterceptors extends InterceptorsWrapper {
         await _sharedPreferencesManager.putInt(SharedPreferencesManager.keyCountRefreshToken, refreshCount!+1);
       }
     } catch (error) {
-      print("Error dio: ${error.toString()}");
+      debugPrint("Error dio: ${error.toString()}");
       eventBus.fire("logout");
       // handlingError(NavigationService.navigatorKey.currentContext, 3);
     }

+ 24 - 24
pubspec.lock

@@ -213,42 +213,42 @@ packages:
     dependency: "direct main"
     description:
       name: camera
-      sha256: d6ec2cbdbe2fa8f5e0d07d8c06368fe4effa985a4a5ddade9cc58a8cd849557d
+      sha256: "034c38cb8014d29698dcae6d20276688a1bf74e6487dfeb274d70ea05d5f7777"
       url: "https://pub.dev"
     source: hosted
-    version: "0.11.2"
+    version: "0.12.0+1"
   camera_android:
     dependency: "direct main"
     description:
       name: camera_android
-      sha256: "4f40d053a67e99029b5be7f00ef8047b63edb65ccc4e2546b84d47e302c6bf62"
+      sha256: "6b2e240022c61bc89711717c59264a2650c569d2dd4bf513228bd1fc45e189e0"
       url: "https://pub.dev"
     source: hosted
-    version: "0.10.10+4"
+    version: "0.10.10+16"
   camera_android_camerax:
     dependency: transitive
     description:
       name: camera_android_camerax
-      sha256: "7cc6adf1868bdcf4e63a56b24b41692dfbad2bec1cdceea451c77798f6a605c3"
+      sha256: b5064cf25a2787d122d0bf12e77c7b1033a2b983d0730e3091f770ee376efde5
       url: "https://pub.dev"
     source: hosted
-    version: "0.6.13"
+    version: "0.7.2"
   camera_avfoundation:
     dependency: transitive
     description:
       name: camera_avfoundation
-      sha256: eff7ed630b1ac3994737c790368fe006388ad9f271d7148e432263721e45dc75
+      sha256: "90e4cc3fde331581a3b2d35d83be41dbb7393af0ab857eb27b732174289cb96d"
       url: "https://pub.dev"
     source: hosted
-    version: "0.9.18+7"
+    version: "0.10.1"
   camera_platform_interface:
     dependency: transitive
     description:
       name: camera_platform_interface
-      sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2"
+      sha256: "7ac852d77699acee79f0d438b793feee26721841e50973576419ff5c6d95e9b7"
       url: "https://pub.dev"
     source: hosted
-    version: "2.10.0"
+    version: "2.13.0"
   camera_web:
     dependency: transitive
     description:
@@ -277,10 +277,10 @@ packages:
     dependency: transitive
     description:
       name: characters
-      sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+      sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
       url: "https://pub.dev"
     source: hosted
-    version: "1.4.0"
+    version: "1.4.1"
   checked_yaml:
     dependency: transitive
     description:
@@ -1041,18 +1041,18 @@ packages:
     dependency: transitive
     description:
       name: matcher
-      sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+      sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
       url: "https://pub.dev"
     source: hosted
-    version: "0.12.17"
+    version: "0.12.19"
   material_color_utilities:
     dependency: transitive
     description:
       name: material_color_utilities
-      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+      sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
       url: "https://pub.dev"
     source: hosted
-    version: "0.11.1"
+    version: "0.13.0"
   meta:
     dependency: transitive
     description:
@@ -1073,10 +1073,10 @@ packages:
     dependency: "direct main"
     description:
       name: mobile_scanner
-      sha256: "91d28b825784e15572fdc39165c5733099ce0e69c6f6f0964ebdbf98a62130fd"
+      sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce
       url: "https://pub.dev"
     source: hosted
-    version: "6.0.6"
+    version: "7.2.0"
   nested:
     dependency: transitive
     description:
@@ -1425,10 +1425,10 @@ packages:
     dependency: "direct main"
     description:
       name: searchfield
-      sha256: c06d4e7b916472c60926a2b79656e0c55480d60a06aef542d1e8708567da90ad
+      sha256: "09231f4b86c78e2ae03c1ce83b0a9facd44b2ffa8fd9208175a97736092742ba"
       url: "https://pub.dev"
     source: hosted
-    version: "1.3.5"
+    version: "2.0.0"
   selectable_autolink_text:
     dependency: "direct main"
     description:
@@ -1646,10 +1646,10 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
+      sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.7"
+    version: "0.7.10"
   timelines_plus:
     dependency: "direct main"
     description:
@@ -1955,5 +1955,5 @@ packages:
     source: hosted
     version: "3.1.1"
 sdks:
-  dart: ">=3.8.0-0 <4.0.0"
-  flutter: ">=3.27.0"
+  dart: ">=3.9.0 <4.0.0"
+  flutter: ">=3.41.0"

+ 4 - 4
pubspec.yaml

@@ -69,8 +69,8 @@ dependencies:
   synchronized: ^3.3.0+3
 
   # 📁 File & Media Handling
-  camera: ^0.11.2
-  camera_android: ^0.10.10+4
+  camera: ^0.12.0+1
+  camera_android: ^0.10.10+16
   file_picker: ^9.0.0
   image: ^4.5.2
   image_picker: ^1.1.2
@@ -97,13 +97,13 @@ dependencies:
   dotted_line: ^3.2.3
   drag_and_drop_lists: ^0.4.2
   lazy_load_scrollview: ^1.3.0
-  searchfield: ^1.2.5
+  searchfield: ^2.0.0
   selectable_autolink_text: ^2.6.0
   timelines_plus: ^1.0.6
 
   # 📱 Device & Permissions
   app_settings: ^5.1.1
-  mobile_scanner: ^6.0.6
+  mobile_scanner: ^7.2.0
   permission_handler: ^11.3.1
   url_launcher: ^6.3.1