diff --git a/assets/images/device_note.svg b/assets/images/device_note.svg new file mode 100644 index 00000000..f0b94043 --- /dev/null +++ b/assets/images/device_note.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/main.dart b/lib/main.dart index 92b8684c..e57db364 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -40,6 +41,7 @@ class MyApp extends StatelessWidget { PointerDeviceKind.unknown, }, ), + theme: ThemeData( textTheme: const TextTheme( bodySmall: TextStyle(fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold), @@ -53,10 +55,11 @@ class MyApp extends StatelessWidget { fontWeight: FontWeight.bold, ), ), + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: VisitorPasswordDialog() + // home: AddDeviceDialog() home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index e8be33f4..aef9508c 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -171,11 +171,10 @@ class AccessBloc extends Bloc { } - - DateTime timestampToDateTime(dynamic timestamp) { - return DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); + String timestampToDate(dynamic timestamp) { + DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); + return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}"; } - } diff --git a/lib/pages/access_management/model/password_model.dart b/lib/pages/access_management/model/password_model.dart index 0377bfb7..acd3e13a 100644 --- a/lib/pages/access_management/model/password_model.dart +++ b/lib/pages/access_management/model/password_model.dart @@ -1,3 +1,5 @@ +import 'package:syncrow_web/utils/constants/const.dart'; + class PasswordModel { final dynamic passwordId; final dynamic invalidTime; @@ -6,7 +8,7 @@ class PasswordModel { final dynamic createdTime; final dynamic passwodName; // New field final dynamic passwordStatus; - final dynamic passwordType; + final AccessType passwordType; final dynamic deviceUuid; PasswordModel({ @@ -17,7 +19,7 @@ class PasswordModel { this.createdTime, this.passwodName, // New field this.passwordStatus, - this.passwordType, + required this.passwordType, this.deviceUuid, }); @@ -30,7 +32,7 @@ class PasswordModel { createdTime: json['createdTime'], passwodName: json['passwodName']??'No name', // New field passwordStatus: json['passwordStatus'], - passwordType: json['passwordType'], + passwordType:AccessTypeExtension.fromString(json['passwordType']) , deviceUuid: json['deviceUuid'], ); } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index d2dd72b9..265c96c0 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -3,10 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; +import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -51,68 +53,69 @@ class AccessManagementPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - height: size.height * 0.05, - width:size.width * 0.26 , decoration: containerDecoration, - child: Center( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: BlocProvider.of(context).tabs.length, - itemBuilder: (context, index) { - final isSelected = index == - BlocProvider.of(context).selectedIndex; - return InkWell( - onTap: () { - BlocProvider.of(context).add(TabChangedEvent(index)); - }, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.boxColor, - border: Border.all( - color: isSelected ? Colors.blue : Colors.transparent, - width: 2.0,), - borderRadius: index == 0 - ? const BorderRadius.only( - topLeft: Radius.circular(10), - bottomLeft: Radius.circular(10)) - : index == 3 - ? const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10)) - : null), - padding: const EdgeInsets.only(left: 10,right: 10), - child: Center( - child: Text( - BlocProvider.of(context).tabs[index], - style: TextStyle( - color: isSelected - ? Colors.blue - : Colors.black, + height: size.height * 0.05, + child: Flexible( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: BlocProvider.of(context).tabs.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final isSelected = index == + BlocProvider.of(context).selectedIndex; + return InkWell( + onTap: () { + BlocProvider.of(context).add(TabChangedEvent(index)); + }, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.boxColor, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0,), + borderRadius: index == 0 + ? const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10)) + : index == 3 + ? const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10)) + : null), + padding: const EdgeInsets.only(left: 10,right: 10), + child: Center( + child: Text( + BlocProvider.of(context).tabs[index], + style: TextStyle( + color: isSelected + ? Colors.blue + : Colors.black, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), - ), const SizedBox( height: 20, ), Row( children: [ Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - const Text('Password Name'), + const Text('Name'), Container( width: size.width * 0.15, decoration: containerDecoration, child: TextFormField( controller: accessBloc.passwordName, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), decoration: textBoxDecoration()! .copyWith(hintText: 'Please enter'), )), @@ -206,7 +209,7 @@ class AccessManagementPage extends StatelessWidget { ); }, borderRadius: 8, - child: Text('+ Create Visitor Password ')), + child: const Text('+ Create Visitor Password ')), ), const SizedBox( width: 10, @@ -230,120 +233,110 @@ class AccessManagementPage extends StatelessWidget { height: 20, ), Expanded( - child: state is TableLoaded - ? TableWidget(size, state,accessBloc) - : const Center(child: CircularProgressIndicator())) + child: state is TableLoaded + ? DynamicTable( + size: size, + cellDecoration: containerDecoration, + headers: const [ + 'Name', + 'Access Type', + 'Access Period', + 'Device Id', + 'Authorizer', + 'Authorization Date & Time', + 'Access Status' + ], + data: state.data.map((item) { + return [ + item.passwodName.toString(), + item.passwordType.value, + ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), + item.deviceUuid.toString(), + '', + '', + '' + ]; + }).toList(), + ) + : const Center(child: CircularProgressIndicator()), + ) + ], ), ); }))); } - Container TableWidget(Size size, TableLoaded state,AccessBloc accessBloc) { - return Container( - decoration: containerDecoration, - width: size.width, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - Container( - width: size.width, - height: size.height, - child: Column( - children: [ - Container( - color: ColorsManager.boxColor, - child: Row( - children: [ - _buildTableHeaderCell('Password name'), - _buildTableHeaderCell(' Password Type'), - _buildTableHeaderCell('Start Time'), - _buildTableHeaderCell('End Time'), - _buildTableHeaderCell('Device Id'), - // _buildTableHeaderCell('Authorization Source'), - // _buildTableHeaderCell('Authorizer'), - _buildTableHeaderCell('Password Created'), - // _buildTableHeaderCell('Access Status'), - _buildTableHeaderCell('Password Status'), - ], - ), - ), - Expanded( - child: Container( - width: size.width, - color: ColorsManager.whiteColors, - child: ListView( - shrinkWrap: true, - children: [ - Column( - children: state.data.map((item) { - return Row( - children: [ - _buildTableCell(item.passwodName), - _buildTableCell(item.passwordType), - _buildTableCell(accessBloc.timestampToDateTime(item.effectiveTime).toString()), - _buildTableCell(accessBloc.timestampToDateTime(item.invalidTime).toString()), - _buildTableCell(item.deviceUuid.toString()), - // _buildTableCell(item.authorizationSource), - // _buildTableCell(item.authorizer), - _buildTableCell(item.passwordCreated!=null?accessBloc.timestampToDateTime(item.passwordCreated).toString():'no data'), - // _buildTableCell(item.accessStatus), - _buildTableCell(item.passwordStatus.toString()), - ], - ); - }).toList(), - ), - ], - ), - ), - ), - ], - ), - ), - ], - ), - ), - ); - } + // Container TableWidget(Size size, TableLoaded state,AccessBloc accessBloc) { + // return Container( + // decoration: containerDecoration, + // width: size.width, + // child: Padding( + // padding: const EdgeInsets.all(10.0), + // child: ListView( + // scrollDirection: Axis.horizontal, + // children: [ + // Container( + // width: size.width, + // height: size.height, + // child: Column( + // children: [ + // Container( + // color: ColorsManager.boxColor, + // child: Row( + // children: [ + // _buildTableHeaderCell('Password name'), + // _buildTableHeaderCell(' Password Type'), + // _buildTableHeaderCell('Start Time'), + // _buildTableHeaderCell('End Time'), + // _buildTableHeaderCell('Device Id'), + // // _buildTableHeaderCell('Authorization Source'), + // // _buildTableHeaderCell('Authorizer'), + // _buildTableHeaderCell('Password Created'), + // // _buildTableHeaderCell('Access Status'), + // _buildTableHeaderCell('Password Status'), + // ], + // ), + // ), + // Expanded( + // child: Container( + // width: size.width, + // color: ColorsManager.whiteColors, + // child: ListView( + // shrinkWrap: true, + // children: [ + // Column( + // children: state.data.map((item) { + // return Row( + // children: [ + // _buildTableCell(item.passwodName), + // _buildTableCell(item.passwordType), + // + // _buildTableCell(accessBloc.timestampToDateTime(item.effectiveTime).toString()), + // _buildTableCell(accessBloc.timestampToDateTime(item.invalidTime).toString()), + // _buildTableCell(item.deviceUuid.toString()), + // // _buildTableCell(item.authorizationSource), + // // _buildTableCell(item.authorizer), + // _buildTableCell(item.passwordCreated!=null?accessBloc.timestampToDateTime(item.passwordCreated).toString():'no data'), + // // _buildTableCell(item.accessStatus), + // _buildTableCell(item.passwordStatus.toString()), + // ], + // ); + // }).toList(), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ); + // } } -Widget _buildTableHeaderCell(String title) { - return Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border.symmetric( - vertical: BorderSide(color: ColorsManager.boxDivider))), - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ); -} - -Widget _buildTableCell(String content) { - return Expanded( - child: Container( - height: 80, - padding: const EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( // <--- right side - color: ColorsManager.boxDivider, - width: 1.0, - ), - ) - ), - alignment: Alignment.centerLeft, - child: Text( - content, - style: TextStyle(color: Colors.black, fontSize: 12), - ), - ), - ); -} diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart new file mode 100644 index 00000000..25c821fb --- /dev/null +++ b/lib/pages/common/custom_table.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicTable extends StatelessWidget { + final List headers; + final List> data; + final BoxDecoration? headerDecoration; + final BoxDecoration? cellDecoration; + final Size size; + + const DynamicTable({ + Key? key, + required this.headers, + required this.data, + required this.size, + this.headerDecoration, + this.cellDecoration, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + + decoration: cellDecoration, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container( + width:size.width, + child: Column( + children: [ + Container( + decoration: headerDecoration ?? + BoxDecoration(color: Colors.grey[200]), + child: Row( + children: headers.map((header) => + _buildTableHeaderCell(header)).toList(), + ), + ), + Expanded( + child: Container( + color: Colors.white, + child: ListView( + shrinkWrap: true, + children: data.map((row) { + return Row( + children: row.map((cell) => + _buildTableCell(cell.toString())).toList(), + ); + }).toList(), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} +Widget _buildTableHeaderCell(String title) { + return Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider))), + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ); +} + +Widget _buildTableCell(String content) { + return Expanded( + child: Container( + height: 80, + padding: const EdgeInsets.all(20.0), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( // <--- right side + color: ColorsManager.boxDivider, + width: 1.0, + ), + ) + ), + alignment: Alignment.centerLeft, + child: Text( + content, + style: TextStyle(color: Colors.black, fontSize: 12), + ), + ), + ); +} + + + diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index 003ca88d..0662199b 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -29,8 +29,7 @@ class CustomWebTextField extends StatelessWidget { if(isRequired) Row( children: [ - Text( - '* ', + Text('* ', style: Theme.of(context) .textTheme .bodyMedium! @@ -39,6 +38,7 @@ class CustomWebTextField extends StatelessWidget { Text(textFieldName), ], ), + const SizedBox(width: 10,), Text( description??'', // ' The password will be sent to the visitor’s email address.', style: Theme.of(context) @@ -53,7 +53,17 @@ class CustomWebTextField extends StatelessWidget { ), const SizedBox(height: 7,), Container( - decoration: containerDecoration, + height: MediaQuery.of(context).size.height*0.05, + decoration: containerDecoration.copyWith( + color: const Color(0xFFF5F6F7), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius:2, + blurRadius: 3, + offset: Offset(1, 1), // changes position of shadow + ), ] + ), child: TextFormField( controller: controller, style: const TextStyle(color: Colors.black), diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 668d082e..c6ce5188 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; @@ -9,15 +11,29 @@ import 'package:syncrow_web/utils/snack_bar.dart'; class VisitorPasswordBloc extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { on(selectUsageFrequency); + on(_onFetchDevice); + on(selectAccessType); on(selectTimeVisitorPassword); on(toggleRepeat); on(toggleDaySelection); - } final TextEditingController userNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); + + + final TextEditingController deviceNameController = TextEditingController(); + final TextEditingController deviceIdController = TextEditingController(); + final TextEditingController unitNameController = TextEditingController(); + final TextEditingController virtualAddressController = TextEditingController(); + + List data=[]; + + + + + String accessTypeSelected='Offline Password'; String usageFrequencySelected='One-Time'; @@ -136,6 +152,17 @@ class VisitorPasswordBloc extends Bloc _onFetchDevice( + FetchDevice event, Emitter emit) async { + try { + emit(DeviceLoaded()); + data = await AccessMangApi().fetchDevices(); + emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index b0a51ca0..8195e155 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -47,3 +47,4 @@ class ToggleDaySelectionEvent extends VisitorPasswordEvent { class ToggleRepeatEvent extends VisitorPasswordEvent {} +class FetchDevice extends VisitorPasswordEvent {} diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 8a5525d3..88b14ec9 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -1,6 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; abstract class VisitorPasswordState extends Equatable { const VisitorPasswordState(); @@ -42,3 +43,20 @@ class IsRepeatState extends VisitorPasswordState { class LoadingInitialState extends VisitorPasswordState {} class ChangeTimeState extends VisitorPasswordState {} +class DeviceLoaded extends VisitorPasswordState {} +class FailedState extends VisitorPasswordState { + final String message; + + FailedState(this.message); + + @override + List get props => [message]; +} +class TableLoaded extends VisitorPasswordState { + final List data; + + const TableLoaded(this.data); + + @override + List get props => [data]; +} \ No newline at end of file diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart new file mode 100644 index 00000000..0b37d70d --- /dev/null +++ b/lib/pages/visitor_password/model/device_model.dart @@ -0,0 +1,99 @@ + + +class DeviceModel { + dynamic productUuid; + dynamic productType; + dynamic activeTime; + dynamic category; + dynamic categoryName; + dynamic createTime; + dynamic gatewayId; + dynamic icon; + dynamic ip; + dynamic lat; + dynamic localKey; + dynamic lon; + dynamic model; + dynamic name; + dynamic online; + dynamic ownerId; + dynamic sub; + dynamic timeZone; + dynamic updateTime; + dynamic uuid; + + DeviceModel({ + required this.productUuid, + required this.productType, + required this.activeTime, + required this.category, + required this.categoryName, + required this.createTime, + required this.gatewayId, + required this.icon, + required this.ip, + required this.lat, + required this.localKey, + required this.lon, + required this.model, + required this.name, + required this.online, + required this.ownerId, + required this.sub, + required this.timeZone, + required this.updateTime, + required this.uuid, + }); + + // Deserialize from JSON + factory DeviceModel.fromJson(Map json) { + return DeviceModel( + productUuid: json['productUuid'] , + productType: json['productType'], + activeTime: json['activeTime'], + category: json['category'] , + categoryName: json['categoryName'] , + createTime: json['createTime'] , + gatewayId: json['gatewayId'], + icon: json['icon'], + ip: json['ip'] , + lat: json['lat'] , + localKey: json['localKey'] , + lon: json['lon'] , + model: json['model'] , + name: json['name'], + online: json['online'], + ownerId: json['ownerId'] , + sub: json['sub'], + timeZone: json['timeZone'], + updateTime: json['updateTime'] , + uuid: json['uuid'], + ); + } + + // Serialize to JSON + Map toJson() { + return { + 'productUuid': productUuid, + 'productType': productType, + 'activeTime': activeTime, + 'category': category, + 'categoryName': categoryName, + 'createTime': createTime, + 'gatewayId': gatewayId, + 'icon': icon, + 'ip': ip, + 'lat': lat, + 'localKey': localKey, + 'lon': lon, + 'model': model, + 'name': name, + 'online': online, + 'ownerId': ownerId, + 'sub': sub, + 'timeZone': timeZone, + 'updateTime': updateTime, + 'uuid': uuid, + }; + } +} diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart new file mode 100644 index 00000000..d987621e --- /dev/null +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -0,0 +1,210 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; +import '../../common/custom_table.dart'; + + +class AddDeviceDialog extends StatelessWidget { + const AddDeviceDialog({super.key}); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return BlocProvider( + create: (context) => VisitorPasswordBloc()..add(FetchDevice()), + child: BlocBuilder( + builder: (BuildContext context, VisitorPasswordState state) { + final visitorBloc = BlocProvider.of(context); + return AlertDialog( + backgroundColor: Colors.white, + title: const Text('Add Accessible Device'), + content: Container( + height: MediaQuery.of(context).size.height/1.7, + width: MediaQuery.of(context).size.width/2, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + Container( + width: size.width, + padding: EdgeInsets.all(15), + decoration:containerDecoration.copyWith( + color: ColorsManager.worningColor, + border: Border.all(color: Color(0xffFFD22F)), + boxShadow: [] + ), + child: Row( + children: [ + SizedBox( + child: SvgPicture.asset( + Assets.deviceNoteIcon, + height: 15, + width: 15, + ), + ), + SizedBox(width: 10,), + Text('Only online accessible devices can be added'), + ], + ) + ), + SizedBox(height: 20,), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.deviceNameController, + isRequired: true, + textFieldName: 'Device Name', + description: '', + ), + ), + const SizedBox(width: 10), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.deviceNameController, + isRequired: true, + textFieldName: 'Device ID', + description: '', + ), + ), + const SizedBox(width: 10), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.unitNameController, + isRequired: true, + textFieldName: 'Unit Name', + description: '', + ), + ), + + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 25), + Center( + child: Container( + height: 43, + width: 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + onPressed: () { + // Your search function here + }, + borderRadius: 9, + child: const Text('Search'), + ), + ), + ), + ), + + ], + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 25), + + Center( + child: Container( + height: 43, + width: 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text( + 'Reset', + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black), + ), + ), + ), + ), + ), + + ], + ), + + ], + ), + SizedBox(height: 20), + Container( + child: Expanded( + child: state is TableLoaded + ? Container( + decoration: containerDecoration, + child: DynamicTable( + size: size*0.5, + headers: ['Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], + data: state.data.map((item) { + return [ + item.name.toString(), + item.uuid.toString(), + item.productType.toString(), + '', + item.online.toString(), + // item.categoryName.toString(), + // accessBloc.timestampToDateTime(item.effectiveTime).toString(), + // accessBloc.timestampToDateTime(item.invalidTime).toString(), + // item.deviceUuid.toString(), + // item.passwordCreated != null ? accessBloc.timestampToDateTime(item.passwordCreated).toString() : 'no data', + // item.passwordStatus.toString(), + ]; + }).toList(), + ), + ) + // TableWidget(size: size, headers: ['Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status', 'Virtual Address',], data: [], bloc: bloc) + : const Center(child: CircularProgressIndicator())), + ) + ], + ), + ), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.2, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!, + ), + ), + ), + Container( + decoration: containerDecoration, + width: size.width * 0.2, + child: const DefaultButton( + borderRadius: 8, + child: Text('Ok'), + ), + ), + ], + ); + }, + ), + ); } +} diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 140ded0b..0693f724 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart'; -import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/style.dart'; class VisitorPasswordDialog extends StatelessWidget { @@ -24,216 +24,242 @@ class VisitorPasswordDialog extends StatelessWidget { final visitorBloc = BlocProvider.of(context); bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; return AlertDialog( + backgroundColor: Colors.white, title: const Text('Create visitor password'), content: SingleChildScrollView( - child: ListBody( - children: [ - Row( - children: [ - Expanded( - flex: 2, - child: CustomWebTextField( - controller: visitorBloc.userNameController, - isRequired: true, - textFieldName: 'User Name', - description: '', - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: CustomWebTextField( - controller: visitorBloc.emailController, - isRequired: true, - textFieldName: 'Email Address', - description: 'The password will be sent to the visitor’s email address.', - ), - ), - ], - ), - SizedBox(height: size.height * 0.02), // Add spacing - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListBody( + children: [ + Container( + child: Row( children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.userNameController, + isRequired: true, + textFieldName: 'Name', + description: '', + ), ), - const Text('Access Type'), + const Spacer(), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.emailController, + isRequired: true, + textFieldName: 'Email Address', + description: 'The password will be sent to the visitor’s email address.', + ), + ), + const Spacer(), ], ), - Row( - children: [ - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Offline Password'), - value: 'Offline Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + ), + SizedBox(height: 20,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Online Password'), - value: 'Online Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + const Text('Access Type'), + ], + ), + Row( + children: [ + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Offline Password'), + value: 'Offline Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Dynamic Password'), - value: 'Dynamic Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Online Password'), + value: 'Online Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), ), - ), - ], - ), - ], - ), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Usage Frequency'), - ], - ), - Row( - children: [ - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('One-Time'), - value: 'One-Time', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Dynamic Password'), + value: 'Dynamic Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), ), - ), - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('Periodic'), - value: 'Periodic', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, + ], + ), + const Text('Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password'), + SizedBox(height: 20,) + ], + ), + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), ), - ), - ], - ), - ], - ), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): + const Text('Usage Frequency'), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('One-Time'), + value: 'One-Time', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectUsageFrequency(value)); + } + }, + ), + ), + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('Periodic'), + value: 'Periodic', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectUsageFrequency(value)); + } + }, + ), + ), + ], + ), - DateTimeWebWidget( - isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); - }, - startTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); - }, - firstString: visitorBloc.startTime, - secondString: visitorBloc.endTime, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Access Devices'), - ], - ), - const Text('Within the validity period, each device can be unlocked only once.'), - visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password'? - SizedBox( - width: 100, - child: ListTile( - contentPadding: EdgeInsets.zero, - leading: const Text('Repeat'), - trailing: Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, + Text('Within the validity period, each device can be unlocked only once.') + ], + ), + + const SizedBox(height: 20,), + + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + DateTimeWebWidget( + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + }, + firstString: visitorBloc.startTime, + secondString: visitorBloc.endTime, + ), + const SizedBox(height: 20,), + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Devices'), + ], + ), + const Text('Within the validity period, each device can be unlocked only once.'), + const SizedBox(height: 20,), + visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password'? + SizedBox( + width: 100, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const Text('Repeat'), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), ), ), - ), - ):const SizedBox(), - isRepeat ? const RepeatWidget() : const SizedBox(), - Container( - decoration: containerDecoration, - width: size.width * 0.2, - child: const DefaultButton( - borderRadius: 8, - child: Text('+ Add Device'), - ), - ), - ], - ), + ):const SizedBox(), + isRepeat ? const RepeatWidget() : const SizedBox(), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddDeviceDialog(); - ], + }, + ); + }, + borderRadius: 8, + child: Text('+ Add Device'), + ), + ), + ], + ), + + ], + ), ), ), actionsAlignment: MainAxisAlignment.center, diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 49870143..de14f4ea 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -6,6 +6,8 @@ import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; +import '../pages/visitor_password/model/device_model.dart'; + class AccessMangApi{ @@ -31,6 +33,27 @@ class AccessMangApi{ } } + Future fetchDevices() async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.getDevices, + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('fetchDevices List: $json'); + List passwordList = jsonData.map((jsonItem) { + return DeviceModel.fromJson(jsonItem); + }).toList(); + return passwordList; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 7be7f328..0942b47b 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -46,8 +46,10 @@ class AuthenticationAPI { }, showServerMessage: true, expectedResponseModel: (json) { + print('object==$json'); return 30; } + ); return 30; } on DioException catch (e) { diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 01e539dc..9b5b6d44 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -18,6 +18,7 @@ abstract class ColorsManager { static const Color dozeColor = Color(0xFFFEC258); static const Color relaxColor = Color(0xFFFBD288); static const Color readingColor = Color(0xFFF7D69C); + static const Color worningColor = Color(0xFFFFF3C8); static const Color energizingColor = Color(0xFFEDEDED); static const Color dividerColor = Color(0xFFEBEBEB); static const Color slidingBlueColor = Color(0x99023DFE); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index d689ce79..b9ee2f5e 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -10,5 +10,6 @@ abstract class ApiEndpoints { static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; static const String getRegion = '$baseUrl/region'; static const String visitorPassword = '$baseUrl/visitor-password'; + static const String getDevices = '$baseUrl/visitor-password/devices'; static const String getUser = '$baseUrl/user/{userUuid}'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d53f82a7..2c2a6e43 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -24,4 +24,5 @@ class Assets { static const String integrationsIcon = "assets/images/Integrations_icon.svg"; static const String assetIcon = "assets/images/asset_icon.svg"; static const String calendarIcon = "assets/images/calendar_icon.svg"; + static const String deviceNoteIcon = "assets/images/device_note.svg"; } diff --git a/lib/utils/constants/const.dart b/lib/utils/constants/const.dart new file mode 100644 index 00000000..3d48dce6 --- /dev/null +++ b/lib/utils/constants/const.dart @@ -0,0 +1,44 @@ + + + +enum AccessType { + onlineOnetime, + onlineMultiple, + offlineOnetime, + offlineMultiple, +} + +extension AccessTypeExtension on AccessType { + String get value { + switch (this) { + case AccessType.onlineOnetime: + return "Online Password"; + case AccessType.onlineMultiple: + return "online Multiple Password"; + case AccessType.offlineOnetime: + return "Offline Onetime Password"; + case AccessType.offlineMultiple: + return "Offline Multiple Password"; + } + } + + static AccessType fromString(String value) { + switch (value) { + case "ONLINE_ONETIME": + return AccessType.onlineOnetime; + case "ONLINE_MULTIPLE": + return AccessType.onlineMultiple; + case "OFFLINE_ONETIME": + return AccessType.offlineOnetime; + case "OFFLINE_MULTIPLE": + return AccessType.offlineMultiple; + default: + throw ArgumentError("Invalid access type: $value"); + } + } +} + + + + + diff --git a/lib/utils/constants/string_const.dart b/lib/utils/constants/string_const.dart deleted file mode 100644 index 3f2ff2d6..00000000 --- a/lib/utils/constants/string_const.dart +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 8c174468..24747880 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -6,7 +6,7 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration suffixIcon:suffixIcon? const Icon(Icons.search):null, hintText: 'Search', filled: true, // Enable background filling - fillColor: Colors.grey.shade200, // Set the background color + fillColor: const Color(0xffF5F6F7), // Set the background color border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), // Add border radius borderSide: BorderSide.none, // Remove the underline @@ -30,16 +30,17 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration ); - -Decoration containerDecoration = BoxDecoration( +BoxDecoration containerDecoration = BoxDecoration( boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 8, - offset: Offset(0, + offset: const Offset(0, 3), // changes position of shadow ), ], color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))); \ No newline at end of file + borderRadius: const BorderRadius.all(Radius.circular(10))); + +