WIP
This commit is contained in:
parent
67edb919a0
commit
f493c0744e
|
@ -15,22 +15,9 @@ import 'package:mime_type/mime_type.dart';
|
|||
import '/flutter_flow/uploaded_file.dart';
|
||||
import 'get_streamed_response.dart';
|
||||
|
||||
enum ApiCallType {
|
||||
GET,
|
||||
POST,
|
||||
DELETE,
|
||||
PUT,
|
||||
PATCH,
|
||||
}
|
||||
enum ApiCallType { GET, POST, DELETE, PUT, PATCH }
|
||||
|
||||
enum BodyType {
|
||||
NONE,
|
||||
JSON,
|
||||
TEXT,
|
||||
X_WWW_FORM_URL_ENCODED,
|
||||
MULTIPART,
|
||||
BLOB,
|
||||
}
|
||||
enum BodyType { NONE, JSON, TEXT, X_WWW_FORM_URL_ENCODED, MULTIPART, BLOB }
|
||||
|
||||
class ApiCallOptions extends Equatable {
|
||||
const ApiCallOptions({
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:http/http.dart';
|
||||
import 'package:hub/backend/api_requests/api_calls.dart';
|
||||
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
|
||||
import 'package:hub/custom_code/actions/convert_to_upload_file.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_util.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_widgets.dart';
|
||||
|
|
|
@ -3,12 +3,12 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hub/shared/utils/dialog_util.dart';
|
||||
import 'package:hub/shared/utils/image_util.dart';
|
||||
import 'package:hub/shared/utils/validator_util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '/backend/api_requests/api_calls.dart';
|
||||
import '/custom_code/actions/index.dart' as actions;
|
||||
import '/flutter_flow/flutter_flow_drop_down.dart';
|
||||
import '/flutter_flow/flutter_flow_theme.dart';
|
||||
import '/flutter_flow/flutter_flow_util.dart';
|
||||
|
@ -843,7 +843,7 @@ class _RegisiterVistorTemplateComponentWidgetState
|
|||
onPressed: _isFormValid(context)
|
||||
? () async {
|
||||
_model.imgBase64 =
|
||||
await actions.convertImageFileToBase64(
|
||||
await ImageUtils.convertImageFileToBase64(
|
||||
_model.uploadedLocalFile,
|
||||
);
|
||||
_model.scheduleVisitor =
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:hub/backend/api_requests/api_calls.dart';
|
||||
import 'package:hub/backend/api_requests/api_manager.dart';
|
||||
import 'package:hub/custom_code/actions/get_dev_u_u_i_d.dart';
|
||||
import 'package:hub/flutter_flow/nav/nav.dart';
|
||||
import 'package:hub/flutter_flow/random_data_util.dart';
|
||||
import 'package:hub/pages/home_page/home_page_model.dart';
|
||||
import 'package:hub/shared/helpers/db_helper.dart';
|
||||
import 'package:hub/shared/utils/device_util.dart';
|
||||
import 'package:hub/shared/utils/dialog_util.dart';
|
||||
import 'package:hub/shared/utils/log_util.dart';
|
||||
|
||||
|
@ -108,7 +108,7 @@ class SignInTemplateComponentModel
|
|||
email = emailAdress!;
|
||||
passwd = password!;
|
||||
|
||||
devUUID = await getDevUUID();
|
||||
devUUID = await DeviceUtil.getDevUUID();
|
||||
|
||||
if ((email != '') && (passwd != '')) {
|
||||
AppState().email = email;
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
|
||||
// Automatic FlutterFlow imports
|
||||
|
||||
import 'index.dart'; // Imports other custom actions
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import '../../flutter_flow/uploaded_file.dart';
|
||||
|
||||
Future<String?> convertImageFileToBase64(FFUploadedFile imageFile) async {
|
||||
List<int>? imageBytes = imageFile.bytes;
|
||||
if (imageBytes != null) {
|
||||
String base64Image = base64Encode(imageBytes);
|
||||
return base64Image;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// Automatic FlutterFlow imports
|
||||
import '/backend/schema/structs/index.dart';
|
||||
import '/backend/schema/enums/enums.dart';
|
||||
import '/flutter_flow/flutter_flow_theme.dart';
|
||||
import '/flutter_flow/flutter_flow_util.dart';
|
||||
import 'index.dart'; // Imports other custom actions
|
||||
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
|
||||
import 'package:flutter/material.dart';
|
||||
// Begin custom action code
|
||||
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
|
||||
|
||||
import 'dart:convert'; // Import for base64 decoding
|
||||
import 'dart:io'; // Import for file operations
|
||||
import 'package:path_provider/path_provider.dart'; // Import for temporary directory
|
||||
|
||||
Future<FFUploadedFile> convertToUploadFile(String img) async {
|
||||
// Decode the base64 string into bytes
|
||||
Uint8List bytes = base64.decode(img);
|
||||
|
||||
// Create a temporary file to store the image
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempPath = tempDir.path;
|
||||
final tempFile = await File('$tempPath/image.jpg').create();
|
||||
await tempFile.writeAsBytes(bytes);
|
||||
|
||||
// Create an FFUploadedFile object using the temporary file
|
||||
return FFUploadedFile(
|
||||
bytes: bytes,
|
||||
name: 'image.jpg',
|
||||
);
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import '/backend/schema/structs/index.dart';
|
||||
import 'dart:developer';
|
||||
import '/backend/schema/enums/enums.dart';
|
||||
import '/flutter_flow/flutter_flow_theme.dart';
|
||||
import '/flutter_flow/flutter_flow_util.dart';
|
||||
import 'index.dart'; // Imports other custom actions
|
||||
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
Future<String?> getDevUUID() async {
|
||||
var deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isIOS) {
|
||||
// import 'dart:io'
|
||||
var iosDeviceInfo = await deviceInfo.iosInfo;
|
||||
return iosDeviceInfo.identifierForVendor; // unique ID on iOS
|
||||
} else if (Platform.isAndroid) {
|
||||
var androidDeviceInfo = await deviceInfo.androidInfo;
|
||||
return androidDeviceInfo.id; // unique ID on Android
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getSerialNumber() async {
|
||||
var deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isIOS) {
|
||||
// import 'dart:io'
|
||||
var iosDeviceInfo = await deviceInfo.iosInfo;
|
||||
return iosDeviceInfo.identifierForVendor; // unique ID on iOS
|
||||
} else if (Platform.isAndroid) {
|
||||
var androidDeviceInfo = await deviceInfo.androidInfo;
|
||||
return androidDeviceInfo.serialNumber; // unique ID on Android
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export 'convert_image_file_to_base64.dart' show convertImageFileToBase64;
|
||||
export 'convert_to_upload_file.dart' show convertToUploadFile;
|
||||
export 'get_dev_u_u_i_d.dart' show getDevUUID;
|
|
@ -1 +0,0 @@
|
|||
export 'image_cropper.dart' show ImageCropper;
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
LinkedHashMap<String, dynamic> menu = LinkedHashMap.from({
|
||||
"Schedule":
|
||||
[{
|
||||
"title": "Schedule\nVisit",
|
||||
"icon": Icons.settings,
|
||||
"route": "/Schedule",
|
||||
},
|
||||
{
|
||||
"title": "Complete\Schedule",
|
||||
"icon": Icons.calendar_today,
|
||||
"route": "/Complete",
|
||||
},
|
||||
{
|
||||
"title": "Provisional\Schedule",
|
||||
"icon": Icons.calendar_today,
|
||||
"route": "/Provisional",
|
||||
},
|
||||
{
|
||||
"title": "Fast\Schedule",
|
||||
"icon": Icons.calendar_today,
|
||||
"route": "/Fast",
|
||||
}],
|
||||
"Consult":
|
||||
[{
|
||||
"title": "Consult",
|
||||
"icon": Icons.search,
|
||||
"route": "/consult",
|
||||
},
|
||||
{
|
||||
"title": "Liberation\nConsult",
|
||||
"icon": Icons.search,
|
||||
"route": "/Liberation",
|
||||
},
|
||||
{
|
||||
"title": "Access\nConsult",
|
||||
"icon": Icons.search,
|
||||
"route": "/Access",
|
||||
},
|
||||
{
|
||||
"title": "Poeple\nConsult",
|
||||
"icon": Icons.search,
|
||||
"route": "/Poeple",
|
||||
},
|
||||
{
|
||||
"title": "QR Code\nConsult",
|
||||
"icon": Icons.search,
|
||||
"route": "/qrcode",
|
||||
}],
|
||||
"Preferences":
|
||||
[{
|
||||
"title": "Preferences",
|
||||
"icon": Icons.settings,
|
||||
"route": "/preferences",
|
||||
}],
|
||||
|
||||
});
|
|
@ -9,48 +9,23 @@ import 'lat_lng.dart';
|
|||
import 'place.dart';
|
||||
import 'uploaded_file.dart';
|
||||
|
||||
|
||||
String? isOneAcliID(String jsonString) {
|
||||
// Converte o JSON em um Map
|
||||
final Map<String, dynamic> data = jsonDecode(jsonString);
|
||||
|
||||
// Extrai a lista de locais
|
||||
final List<dynamic> locais = data['locais'];
|
||||
|
||||
// Filtra os locais onde CLU_STATUS é igual a "A"
|
||||
final List<dynamic> locaisAtivos =
|
||||
locais.where((local) => local['CLU_STATUS'] == 'A').toList();
|
||||
|
||||
// Verifica se existe somente um local ativo
|
||||
if (locaisAtivos.length == 1) {
|
||||
return locaisAtivos.first['CLI_ID'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? convertToUppercase(String input) {
|
||||
// recebe uuma string qualquer e retorna ela em uppercase
|
||||
return input.toUpperCase();
|
||||
}
|
||||
|
||||
String? uploadFileToBase64(String? uploadFile) {
|
||||
// takes an UploadFile of an image as an argument and returns a base64 string of that image
|
||||
if (uploadFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Converte a string em uma lista de inteiros
|
||||
final List<int> bytes = uploadFile.codeUnits;
|
||||
|
||||
// Converte os bytes em uma string base64
|
||||
final String base64String = base64.encode(bytes);
|
||||
|
||||
return base64String;
|
||||
}
|
||||
|
||||
String jsonListToStr(List<dynamic> visitorList) {
|
||||
// rece um json list e retorna uma string de $.visitante.VTE_DOCUMENTO separado por virgula
|
||||
String result = '';
|
||||
for (var visitor in visitorList) {
|
||||
result += '${visitor['VTE_DOCUMENTO']},';
|
||||
|
@ -62,7 +37,6 @@ List<dynamic> listStrJsonToJsonList(
|
|||
dynamic jsonList,
|
||||
List<String> strList,
|
||||
) {
|
||||
// Recebe um jsonList e uma stringList como argumento e retorna uma nova jsonList associando cada item
|
||||
List<dynamic> result = [];
|
||||
|
||||
for (int i = 0; i < jsonList.length; i++) {
|
||||
|
@ -79,13 +53,10 @@ List<dynamic> listStrJsonToJsonList(
|
|||
}
|
||||
|
||||
String strListToStr(List<String> strList) {
|
||||
// recebe uma stringList como argumento e retorna uma str com cada item da strList separado por virgula
|
||||
|
||||
return strList.join(',');
|
||||
}
|
||||
|
||||
int extractIdToStr(String str) {
|
||||
// recebe um uma string e retorna um inteiro com o valor entre '_ID:' e a próxima vírgula ','
|
||||
final idStart = str.indexOf('_ID:') + 4;
|
||||
final idEnd = str.indexOf(',', idStart);
|
||||
final idStr = str.substring(idStart, idEnd);
|
||||
|
@ -93,14 +64,12 @@ int extractIdToStr(String str) {
|
|||
}
|
||||
|
||||
String extractDescToStr(String str) {
|
||||
// recebe um uma string e retorna uma string com o valor entre '_DESCRICAO' e a próxima vírgula ','
|
||||
final startIndex = str.indexOf('_DESCRICAO:') + '_DESCRICAO:'.length;
|
||||
final endIndex = str.indexOf(',', startIndex);
|
||||
return str.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
String jsonToStr(dynamic json) {
|
||||
// recebe um json como parametro, converte-o em string e retorna essa string
|
||||
String jsonString = jsonEncode(json);
|
||||
return jsonString;
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) => GoRouter(
|
|||
FFRoute(
|
||||
name: 'acessHistoryPage',
|
||||
path: '/acessHistoryPage',
|
||||
builder: (context, params) => AcessHistoryPageWidget(opt: const {
|
||||
builder: (context, params) => AccessHistoryScreen(opt: const {
|
||||
'personType': '.*',
|
||||
'accessType': '.*',
|
||||
'search': '.*',
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
export '/pages/acess_history_page/acess_history_page_widget.dart'
|
||||
show AcessHistoryPageWidget;
|
||||
export '/pages/home_page/home_page_widget.dart' show HomePageWidget;
|
||||
export '/pages/liberation_history/liberation_history_widget.dart'
|
||||
export 'pages/acess_history_page/acess_history_page_widget.dart'
|
||||
show AccessHistoryScreen;
|
||||
export 'pages/home_page/home_page_widget.dart' show HomePageWidget;
|
||||
export 'pages/liberation_history/liberation_history_widget.dart'
|
||||
show LiberationHistoryWidget;
|
||||
export '/pages/people_on_the_property_page/people_on_the_property_page_widget.dart'
|
||||
export 'pages/people_on_the_property_page/people_on_the_property_page_widget.dart'
|
||||
show PeopleOnThePropertyPageWidget;
|
||||
export '/pages/preferences_settings_page/preferences_settings_widget.dart'
|
||||
export 'pages/preferences_settings_page/preferences_settings_widget.dart'
|
||||
show PreferencesPageWidget;
|
||||
export '/pages/qr_code_page/qr_code_page_widget.dart' show QrCodePageWidget;
|
||||
export '/pages/register_visitor_page/register_visitor_page_widget.dart'
|
||||
export 'pages/qr_code_page/qr_code_page_widget.dart' show QrCodePageWidget;
|
||||
export 'pages/register_visitor_page/register_visitor_page_widget.dart'
|
||||
show RegisterVisitorPageWidget;
|
||||
export '/pages/schedule_complete_visit_page/schedule_complete_visit_page_widget.dart'
|
||||
export 'pages/schedule_complete_visit_page/schedule_complete_visit_page_widget.dart'
|
||||
show ScheduleCompleteVisitPageWidget;
|
||||
export '/pages/sign_in_page/sign_in_page_widget.dart' show SignInPageWidget;
|
||||
export '/pages/sign_up_page/sign_up_page_widget.dart' show SignUpPageWidget;
|
||||
export '/pages/welcome_page/welcome_page_widget.dart' show WelcomePageWidget;
|
||||
export 'pages/sign_in_page/sign_in_page_widget.dart' show SignInPageWidget;
|
||||
export 'pages/sign_up_page/sign_up_page_widget.dart' show SignUpPageWidget;
|
||||
export 'pages/welcome_page/welcome_page_widget.dart' show WelcomePageWidget;
|
||||
|
|
|
@ -1,16 +1,48 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hub/backend/api_requests/api_calls.dart';
|
||||
import 'package:hub/backend/api_requests/api_manager.dart';
|
||||
import 'package:hub/components/molecular_components/message_opt_modal/opt_modal_widget.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_model.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
|
||||
import 'package:hub/flutter_flow/internationalization.dart';
|
||||
import 'package:hub/flutter_flow/request_manager.dart';
|
||||
import 'package:hub/pages/acess_history_page/acess_history_page_widget.dart';
|
||||
import 'package:hub/shared/helpers/db_helper.dart';
|
||||
import 'package:hub/shared/utils/dialog_util.dart';
|
||||
import 'package:hub/shared/utils/log_util.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
class AccessHistoryModel extends FlutterFlowModel<AccessHistoryScreen> {
|
||||
// Singleton instance
|
||||
static final AccessHistoryModel _instance = AccessHistoryModel._internal();
|
||||
|
||||
// Private constructor
|
||||
AccessHistoryModel._internal() : super();
|
||||
|
||||
// Factory constructor to return the singleton instance
|
||||
factory AccessHistoryModel() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
class AcessHistoryPageModel extends FlutterFlowModel<AcessHistoryPageWidget> {
|
||||
final DatabaseHelper db = DatabaseHelper();
|
||||
late final String devUUID;
|
||||
late final String userUUID;
|
||||
late final String cliUUID;
|
||||
final BehaviorSubject<Map<String, String>> selectedTypeSubject;
|
||||
bool isSubjectClosed = false;
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
late Function(Function) safeSetState;
|
||||
|
||||
late ScrollController scrollController;
|
||||
int pageNumber = 1;
|
||||
final int pageSize = 10;
|
||||
bool hasData = false;
|
||||
bool loading = false;
|
||||
|
||||
String personType = '.*';
|
||||
|
||||
late Future<void> accessFuture;
|
||||
List<dynamic> accessWrap = [];
|
||||
|
||||
final unfocusNode = FocusNode();
|
||||
final _accessHistoryManager = FutureRequestManager<ApiCallResponse>();
|
||||
|
@ -30,10 +62,10 @@ class AcessHistoryPageModel extends FlutterFlowModel<AcessHistoryPageWidget> {
|
|||
|
||||
@override
|
||||
void initState(BuildContext context) {
|
||||
initDatabase();
|
||||
_initAsync();
|
||||
}
|
||||
|
||||
Future<void> initDatabase() async {
|
||||
Future<void> _initAsync() async {
|
||||
devUUID = await db
|
||||
.get(key: 'devUUID', field: 'value')
|
||||
.then((value) => value.toString());
|
||||
|
@ -45,11 +77,95 @@ class AcessHistoryPageModel extends FlutterFlowModel<AcessHistoryPageWidget> {
|
|||
.then((value) => value.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unfocusNode.dispose();
|
||||
void _updateAccessHistoryAction(
|
||||
Map<String, String> newType, BuildContext context) {
|
||||
if (!isSubjectClosed) {
|
||||
final currentType = selectedTypeSubject.value;
|
||||
final updatedType = Map<String, String>.from(currentType);
|
||||
bool needsUpdate = false;
|
||||
newType.forEach((key, newValue) {
|
||||
if (currentType[key] != newValue) {
|
||||
updatedType[key] = newValue;
|
||||
needsUpdate = true;
|
||||
}
|
||||
});
|
||||
if (needsUpdate) {
|
||||
selectedTypeSubject.add(updatedType);
|
||||
fetchCardListViewService(updatedType, context);
|
||||
safeSetState?.call(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearAccessHistoryCache();
|
||||
Future<ApiCallResponse?> fetchAccessHistoryService(
|
||||
BuildContext context) async {
|
||||
try {
|
||||
safeSetState.call(() => loading = true);
|
||||
var response = await PhpGroup.getAccessCall.call(
|
||||
pageSize: pageSize.toString(),
|
||||
pageNumber: pageNumber.toString(),
|
||||
pesTipo: personType != 'E' && personType != 'O' ? 'T' : personType,
|
||||
);
|
||||
|
||||
final List<dynamic> accessHistory = response.jsonBody['acessos'] ?? [];
|
||||
|
||||
List<dynamic> filteredAccess = accessHistory.where((item) {
|
||||
final personTypeMatches =
|
||||
personType == '.*' || item["PES_TIPO"].toString() == personType;
|
||||
return personTypeMatches;
|
||||
}).toList();
|
||||
|
||||
if (filteredAccess != null && filteredAccess.isNotEmpty) {
|
||||
safeSetState.call(() {
|
||||
accessWrap.addAll(filteredAccess);
|
||||
hasData = true;
|
||||
loading = false;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
_showNoMoreDataSnackbar(context);
|
||||
safeSetState.call(() {
|
||||
hasData = false;
|
||||
loading = false;
|
||||
});
|
||||
return null;
|
||||
} catch (e, s) {
|
||||
DialogUtil.errorDefault(context);
|
||||
LogUtil.requestAPIFailed('processRequest', "", "Busca Acesso", e, s);
|
||||
safeSetState.call(() {
|
||||
hasData = false;
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _showNoMoreDataSnackbar(BuildContext context) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
FFLocalizations.of(context).getVariableText(
|
||||
ptText: "Não há mais dados.", enText: "No more data."),
|
||||
),
|
||||
duration: const Duration(seconds: 3),
|
||||
backgroundColor: FlutterFlowTheme.of(context).primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _loadMoreAccess(BuildContext context) {
|
||||
if (hasData == true) {
|
||||
pageNumber++;
|
||||
accessFuture = fetchAccessHistoryService(context);
|
||||
}
|
||||
}
|
||||
|
||||
void fetchCardListViewService(
|
||||
Map<String, String> select, BuildContext context) {
|
||||
personType = select['personType']!;
|
||||
accessWrap = [];
|
||||
pageNumber = 1;
|
||||
accessFuture = fetchAccessHistoryService(context);
|
||||
}
|
||||
|
||||
Future toggleOptionsAction(BuildContext context) async {
|
||||
|
@ -71,4 +187,11 @@ class AcessHistoryPageModel extends FlutterFlowModel<AcessHistoryPageWidget> {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unfocusNode.dispose();
|
||||
|
||||
clearAccessHistoryCache();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,36 +15,22 @@ import 'package:hub/shared/utils/log_util.dart';
|
|||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
@immutable
|
||||
class AcessHistoryPageWidget extends StatefulWidget {
|
||||
// ignore: must_be_immutable
|
||||
class AccessHistoryScreen extends StatefulWidget {
|
||||
late Map<String, String> opt = {
|
||||
'personType': '.*',
|
||||
'accessType': '.*',
|
||||
'search': '.*',
|
||||
};
|
||||
AcessHistoryPageWidget({super.key, required this.opt});
|
||||
AccessHistoryScreen({super.key, required this.opt});
|
||||
@override
|
||||
State<AcessHistoryPageWidget> createState() =>
|
||||
_AcessHistoryPageWidgetState(opt);
|
||||
State<AccessHistoryScreen> createState() => _AccessHistoryScreenState(opt);
|
||||
}
|
||||
|
||||
class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
||||
late AcessHistoryPageModel _model;
|
||||
final BehaviorSubject<Map<String, String>> selectedTypeSubject;
|
||||
bool _isSubjectClosed = false;
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
class _AccessHistoryScreenState extends State<AccessHistoryScreen> {
|
||||
late AccessHistoryModel _model;
|
||||
|
||||
late ScrollController _scrollController;
|
||||
int _pageNumber = 1;
|
||||
final int _pageSize = 10;
|
||||
bool _hasData = false;
|
||||
bool _loading = false;
|
||||
|
||||
String _personType = '.*';
|
||||
|
||||
late Future<void> _accessFuture;
|
||||
List<dynamic> _accessWrap = [];
|
||||
|
||||
_AcessHistoryPageWidgetState(Map<String, String> opt)
|
||||
_AccessHistoryScreenState(Map<String, String> opt)
|
||||
: selectedTypeSubject = BehaviorSubject.seeded(opt) {
|
||||
selectedTypeSubject.listen((value) {});
|
||||
}
|
||||
|
@ -52,13 +38,13 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_model = createModel(context, () => AcessHistoryPageModel());
|
||||
_accessFuture = fetchAccessHistoryService();
|
||||
_model = createModel(context, () => AccessHistoryModel());
|
||||
_model.accessFuture = fetchAccessHistoryService();
|
||||
|
||||
_scrollController = ScrollController()
|
||||
_model.scrollController = ScrollController()
|
||||
..addListener(() {
|
||||
if (_scrollController.position.atEdge &&
|
||||
_scrollController.position.pixels != 0) {
|
||||
if (_model.scrollController.position.atEdge &&
|
||||
_model.scrollController.position.pixels != 0) {
|
||||
_loadMoreAccess();
|
||||
}
|
||||
});
|
||||
|
@ -67,7 +53,7 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
@override
|
||||
void dispose() {
|
||||
selectedTypeSubject.close();
|
||||
_isSubjectClosed = true;
|
||||
_model.isSubjectClosed = true;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -76,7 +62,7 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
final theme = FlutterFlowTheme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
key: _model.scaffoldKey,
|
||||
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
|
||||
appBar: _appBar(context, theme),
|
||||
body: _body(context));
|
||||
|
@ -164,84 +150,13 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
void _updateAccessHistoryAction(Map<String, String> newType) {
|
||||
if (!_isSubjectClosed) {
|
||||
final currentType = selectedTypeSubject.value;
|
||||
final updatedType = Map<String, String>.from(currentType);
|
||||
bool needsUpdate = false;
|
||||
newType.forEach((key, newValue) {
|
||||
if (currentType[key] != newValue) {
|
||||
updatedType[key] = newValue;
|
||||
needsUpdate = true;
|
||||
}
|
||||
});
|
||||
if (needsUpdate) {
|
||||
selectedTypeSubject.add(updatedType);
|
||||
fetchCardListViewService(updatedType);
|
||||
safeSetState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<ApiCallResponse?> fetchAccessHistoryService() async {
|
||||
try {
|
||||
setState(() => _loading = true);
|
||||
var response = await PhpGroup.getAccessCall.call(
|
||||
pageSize: _pageSize.toString(),
|
||||
pageNumber: _pageNumber.toString(),
|
||||
pesTipo: _personType != 'E' && _personType != 'O' ? 'T' : _personType,
|
||||
);
|
||||
|
||||
final List<dynamic> accessHistory = response.jsonBody['acessos'] ?? [];
|
||||
|
||||
List<dynamic> filteredAccess = accessHistory.where((item) {
|
||||
final personTypeMatches =
|
||||
_personType == '.*' || item["PES_TIPO"].toString() == _personType;
|
||||
return personTypeMatches;
|
||||
}).toList();
|
||||
|
||||
if (filteredAccess != null && filteredAccess.isNotEmpty) {
|
||||
setState(() {
|
||||
_accessWrap.addAll(filteredAccess);
|
||||
_hasData = true;
|
||||
_loading = false;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
_showNoMoreDataSnackbar(context);
|
||||
setState(() {
|
||||
_hasData = false;
|
||||
_loading = false;
|
||||
});
|
||||
return null;
|
||||
} catch (e, s) {
|
||||
DialogUtil.errorDefault(context);
|
||||
LogUtil.requestAPIFailed('processRequest', "", "Busca Acesso", e, s);
|
||||
setState(() {
|
||||
_hasData = false;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _showNoMoreDataSnackbar(BuildContext context) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
FFLocalizations.of(context).getVariableText(
|
||||
ptText: "Não há mais dados.", enText: "No more data."),
|
||||
),
|
||||
duration: const Duration(seconds: 3),
|
||||
backgroundColor: FlutterFlowTheme.of(context).primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _body(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
if (_hasData == false && _pageNumber <= 1 && _loading == false)
|
||||
if (_model.hasData == false &&
|
||||
_model.pageNumber <= 1 &&
|
||||
_model.loading == false)
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
@ -256,9 +171,9 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
],
|
||||
),
|
||||
)
|
||||
else if (_hasData || _pageNumber >= 1)
|
||||
else if (_model.hasData || _model.pageNumber >= 1)
|
||||
Expanded(child: _cardListViewOrganismWidget()),
|
||||
if (_hasData == true && _loading)
|
||||
if (_model.hasData == true && _model.loading)
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 15),
|
||||
child: Center(
|
||||
|
@ -273,26 +188,12 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
void _loadMoreAccess() {
|
||||
if (_hasData == true) {
|
||||
_pageNumber++;
|
||||
_accessFuture = fetchAccessHistoryService();
|
||||
}
|
||||
}
|
||||
|
||||
void fetchCardListViewService(Map<String, String> select) {
|
||||
_personType = select['personType']!;
|
||||
_accessWrap = [];
|
||||
_pageNumber = 1;
|
||||
_accessFuture = fetchAccessHistoryService();
|
||||
}
|
||||
|
||||
Widget _cardListViewOrganismWidget() {
|
||||
return FutureBuilder<void>(
|
||||
future: _accessFuture,
|
||||
future: _model.accessFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting &&
|
||||
_accessWrap.isEmpty) {
|
||||
_model.accessWrap.isEmpty) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 50.0,
|
||||
|
@ -314,10 +215,10 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
itemCount: _accessWrap.length,
|
||||
controller: _model.scrollController,
|
||||
itemCount: _model.accessWrap.length,
|
||||
itemBuilder: (context, index) {
|
||||
final accessHistoryItem = _accessWrap[index];
|
||||
final accessHistoryItem = _model.accessWrap[index];
|
||||
return _accessHistoryCardMoleculeWidget(context, accessHistoryItem);
|
||||
},
|
||||
);
|
||||
|
@ -396,3 +297,104 @@ class _AcessHistoryPageWidgetState extends State<AcessHistoryPageWidget> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccessHistoryAppBar extends StatelessWidget {
|
||||
AccessHistoryAppBar(this.model, this.selectedTypeSubject);
|
||||
final AccessHistoryModel model;
|
||||
final BehaviorSubject<Map<String, String>> selectedTypeSubject;
|
||||
|
||||
late final FlutterFlowTheme theme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
theme = FlutterFlowTheme.of(context);
|
||||
|
||||
return AppBar(
|
||||
backgroundColor: theme.primaryBackground,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: _backButton(context, theme),
|
||||
title: _title(context, theme),
|
||||
centerTitle: true,
|
||||
elevation: 0.0,
|
||||
actions: [_filterButton(context)],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _backButton(BuildContext context, FlutterFlowTheme theme) {
|
||||
return FlutterFlowIconButton(
|
||||
borderColor: Colors.transparent,
|
||||
borderRadius: 30.0,
|
||||
borderWidth: 1.0,
|
||||
buttonSize: 60.0,
|
||||
icon: Icon(
|
||||
Icons.keyboard_arrow_left,
|
||||
color: theme.primaryText,
|
||||
size: 30.0,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _title(BuildContext context, FlutterFlowTheme theme) {
|
||||
return Text(
|
||||
FFLocalizations.of(context).getText('ch8qymga'),
|
||||
style: theme.headlineMedium.override(
|
||||
fontFamily: theme.headlineMediumFamily,
|
||||
color: theme.primaryText,
|
||||
fontSize: 16.0,
|
||||
letterSpacing: 0.0,
|
||||
useGoogleFonts:
|
||||
GoogleFonts.asMap().containsKey(theme.headlineMediumFamily),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _filterButton(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 10, 0),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.filter_list),
|
||||
onPressed: () async {
|
||||
final Map<String, String>? selectedFilter =
|
||||
await showModalBottomSheet<Map<String, String>>(
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: OptModalWidget(
|
||||
defaultPersonType:
|
||||
selectedTypeSubject.value['personType'] ??
|
||||
'.*',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
if (selectedFilter != null) {
|
||||
_updateAccessHistoryAction(selectedFilter);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccessHistoryList extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: implement build
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_model.dart';
|
||||
import 'package:hub/pages/people_on_the_property_page/people_on_the_property_page_widget.dart';
|
||||
|
|
|
@ -5,15 +5,14 @@ import 'package:flutter/material.dart';
|
|||
import 'package:http/http.dart';
|
||||
import 'package:hub/backend/api_requests/api_calls.dart';
|
||||
import 'package:hub/backend/api_requests/api_manager.dart';
|
||||
import 'package:hub/custom_code/actions/convert_to_upload_file.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_util.dart';
|
||||
import 'package:hub/flutter_flow/form_field_controller.dart';
|
||||
import 'package:hub/pages/pets_page/pets_page_widget.dart';
|
||||
import 'package:hub/shared/helpers/db_helper.dart';
|
||||
import 'package:hub/shared/utils/dialog_util.dart';
|
||||
import 'package:hub/shared/utils/image_util.dart';
|
||||
import 'package:hub/shared/utils/log_util.dart';
|
||||
import 'package:hub/shared/utils/validator_util.dart';
|
||||
import '/custom_code/actions/index.dart' as actions;
|
||||
|
||||
class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
|
||||
final DatabaseHelper db = DatabaseHelper();
|
||||
|
@ -164,7 +163,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
|
|||
Response response = await get(Uri.parse(
|
||||
'https://freaccess.com.br/freaccess/getImage.php?devUUID=$devUUID&userUUID=$userUUID&cliID=$cliUUID&atividade=consultaFotoPet&petId=$petId'));
|
||||
String base64 = base64Encode(response.bodyBytes);
|
||||
uploadedTempFile = await convertToUploadFile(base64);
|
||||
uploadedTempFile = await ImageUtils.convertToUploadFile(base64);
|
||||
updateImage?.call();
|
||||
})();
|
||||
|
||||
|
@ -269,7 +268,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
|
|||
}
|
||||
|
||||
Future<void> updatePet() async {
|
||||
var img = await actions.convertImageFileToBase64(uploadedLocalFile!);
|
||||
var img = await ImageUtils.convertImageFileToBase64(uploadedLocalFile!);
|
||||
img = "base64;jpeg,$img";
|
||||
final url =
|
||||
'https://freaccess.com.br/freaccess/getImage.php?devUUID=${devUUID}&userUUID=${userUUID}&cliID=${cliUUID}&atividade=consultaFotoPet&petId=$petId';
|
||||
|
@ -314,7 +313,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
|
|||
}
|
||||
|
||||
Future<void> registerPet() async {
|
||||
var img = await actions.convertImageFileToBase64(uploadedLocalFile!);
|
||||
var img = await ImageUtils.convertImageFileToBase64(uploadedLocalFile!);
|
||||
img = "base64;jpeg,$img";
|
||||
final response = await PhpGroup.registerPet.call(
|
||||
image: img,
|
||||
|
|
|
@ -18,7 +18,6 @@ import 'package:hub/components/atomic_components/shared_components_atoms/custom_
|
|||
import 'package:hub/components/atomic_components/shared_components_atoms/media_upload_button.dart';
|
||||
import 'package:hub/components/atomic_components/shared_components_atoms/submit_button.dart';
|
||||
import 'package:hub/components/atomic_components/shared_components_atoms/tabview.dart';
|
||||
import 'package:hub/custom_code/actions/convert_to_upload_file.dart';
|
||||
|
||||
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
|
||||
import 'package:hub/flutter_flow/flutter_flow_util.dart';
|
||||
|
@ -28,13 +27,13 @@ import 'package:hub/flutter_flow/internationalization.dart';
|
|||
import 'package:hub/flutter_flow/nav/nav.dart';
|
||||
import 'package:hub/flutter_flow/upload_data.dart';
|
||||
import 'package:hub/pages/pets_page/pets_history_screen.dart';
|
||||
|
||||
import 'package:hub/pages/pets_page/pets_page_model.dart';
|
||||
|
||||
import 'package:hub/shared/utils/dialog_util.dart';
|
||||
import 'package:hub/shared/utils/image_util.dart';
|
||||
import 'package:hub/shared/utils/validator_util.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import '/custom_code/actions/index.dart' as actions;
|
||||
|
||||
class PetsPageWidget extends StatefulWidget {
|
||||
dynamic pet;
|
||||
|
@ -83,7 +82,8 @@ class _PetsPageWidgetState extends State<PetsPageWidget>
|
|||
Response response = await get(Uri.parse(
|
||||
'https://freaccess.com.br/freaccess/getImage.php?devUUID=${_model.devUUID}&userUUID=${_model.userUUID}&cliID=${_model.cliUUID}&atividade=consultaFotoPet&petId=$petId'));
|
||||
String base64 = base64Encode(response.bodyBytes);
|
||||
FFUploadedFile uploadedFile = await convertToUploadFile(base64);
|
||||
FFUploadedFile uploadedFile =
|
||||
await ImageUtils.convertToUploadFile(base64);
|
||||
setState(() {
|
||||
_model.handleUploadComplete(uploadedFile);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import '/backend/schema/structs/index.dart';
|
||||
import 'dart:developer';
|
||||
import '/backend/schema/enums/enums.dart';
|
||||
import '/flutter_flow/flutter_flow_theme.dart';
|
||||
import '/flutter_flow/flutter_flow_util.dart';
|
||||
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
class DeviceUtil {
|
||||
static Future<String?> getDevUUID() async {
|
||||
var deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isIOS) {
|
||||
// import 'dart:io'
|
||||
var iosDeviceInfo = await deviceInfo.iosInfo;
|
||||
return iosDeviceInfo.identifierForVendor; // unique ID on iOS
|
||||
} else if (Platform.isAndroid) {
|
||||
var androidDeviceInfo = await deviceInfo.androidInfo;
|
||||
return androidDeviceInfo.id; // unique ID on Android
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> getSerialNumber() async {
|
||||
var deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isIOS) {
|
||||
// import 'dart:io'
|
||||
var iosDeviceInfo = await deviceInfo.iosInfo;
|
||||
return iosDeviceInfo.identifierForVendor; // unique ID on iOS
|
||||
} else if (Platform.isAndroid) {
|
||||
var androidDeviceInfo = await deviceInfo.androidInfo;
|
||||
return androidDeviceInfo.serialNumber; // unique ID on Android
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Automatic FlutterFlow imports
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import '../../flutter_flow/uploaded_file.dart';
|
||||
|
||||
class ImageUtils {
|
||||
static Future<String?> convertImageFileToBase64(
|
||||
FFUploadedFile imageFile) async {
|
||||
List<int>? imageBytes = imageFile.bytes;
|
||||
if (imageBytes != null) {
|
||||
String base64Image = base64Encode(imageBytes);
|
||||
return base64Image;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<FFUploadedFile> convertToUploadFile(String img) async {
|
||||
// Decode the base64 string into bytes
|
||||
Uint8List bytes = base64.decode(img);
|
||||
|
||||
// Create a temporary file to store the image
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempPath = tempDir.path;
|
||||
final tempFile = await File('$tempPath/image.jpg').create();
|
||||
await tempFile.writeAsBytes(bytes);
|
||||
|
||||
// Create an FFUploadedFile object using the temporary file
|
||||
return FFUploadedFile(
|
||||
bytes: bytes,
|
||||
name: 'image.jpg',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
|
||||
// // LinkedHashMap<String, dynamic> menu = LinkedHashMap.from({
|
||||
// "Schedule":
|
||||
// [{
|
||||
// "title": "Schedule\nVisit",
|
||||
// "icon": Icons.settings,
|
||||
// "route": "/Schedule",
|
||||
// },
|
||||
// {
|
||||
// "title": "Complete\Schedule",
|
||||
// "icon": Icons.calendar_today,
|
||||
// "route": "/Complete",
|
||||
// },
|
||||
// {
|
||||
// "title": "Provisional\Schedule",
|
||||
// "icon": Icons.calendar_today,
|
||||
// "route": "/Provisional",
|
||||
// },
|
||||
// {
|
||||
// "title": "Fast\Schedule",
|
||||
// "icon": Icons.calendar_today,
|
||||
// "route": "/Fast",
|
||||
// }],
|
||||
// "Consult":
|
||||
// [{
|
||||
// "title": "Consult",
|
||||
// "icon": Icons.search,
|
||||
// "route": "/consult",
|
||||
// },
|
||||
// {
|
||||
// "title": "Liberation\nConsult",
|
||||
// "icon": Icons.search,
|
||||
// "route": "/Liberation",
|
||||
// },
|
||||
// {
|
||||
// "title": "Access\nConsult",
|
||||
// "icon": Icons.search,
|
||||
// "route": "/Access",
|
||||
// },
|
||||
// {
|
||||
// "title": "Poeple\nConsult",
|
||||
// "icon": Icons.search,
|
||||
// "route": "/Poeple",
|
||||
// },
|
||||
// {
|
||||
// "title": "QR Code\nConsult",
|
||||
// "icon": Icons.search,
|
||||
// "route": "/qrcode",
|
||||
// }],
|
||||
// "Preferences":
|
||||
// [{
|
||||
// "title": "Preferences",
|
||||
// "icon": Icons.settings,
|
||||
// "route": "/preferences",
|
||||
// }],
|
||||
|
||||
// });
|
Loading…
Reference in New Issue