alerts added and move asset tracking to asset page
parent
caee6edf1b
commit
9c4fd1088c
@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:test_sa/extensions/context_extension.dart';
|
||||
import 'package:test_sa/extensions/int_extensions.dart';
|
||||
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||
import 'package:test_sa/helper/utils.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_model.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_provider.dart';
|
||||
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
|
||||
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||
import 'package:test_sa/views/widgets/item_views/info_date_widget.dart';
|
||||
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
|
||||
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
|
||||
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
|
||||
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
|
||||
import 'package:test_sa/views/widgets/requests/request_status.dart';
|
||||
|
||||
class AlertDetailPage extends StatefulWidget {
|
||||
static const String id = "/alert-detail-page";
|
||||
final int alertId;
|
||||
|
||||
const AlertDetailPage({Key? key, required this.alertId}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AlertDetailPage> createState() => _AlertDetailPageState();
|
||||
}
|
||||
|
||||
class _AlertDetailPageState extends State<AlertDetailPage> {
|
||||
AlertModel _model = AlertModel();
|
||||
late AlertProvider alertProvider;
|
||||
bool isDataUpdated = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
alertProvider = Provider.of<AlertProvider>(context, listen: false);
|
||||
|
||||
return Scaffold(
|
||||
appBar: const DefaultAppBar(title: "Alert Details"),
|
||||
body: FutureBuilder(
|
||||
future: alertProvider.getAlertById(widget.alertId, refresh: isDataUpdated),
|
||||
builder: (context, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting) {
|
||||
return const ALoading();
|
||||
} else if (snap.hasData && snap.data != null) {
|
||||
_model = snap.data as AlertModel;
|
||||
isDataUpdated = false;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: informationCard(_model),
|
||||
).expanded,
|
||||
if (!_model.isResolved)
|
||||
FooterActionButton.footerContainer(
|
||||
context: context,
|
||||
child: AppFilledButton(
|
||||
label: "Resolve",
|
||||
onPressed: () async {
|
||||
Utils.showLoading(context);
|
||||
bool success = await alertProvider.resolveAlert(_model.id!);
|
||||
isDataUpdated = !success;
|
||||
Utils.hideLoading(context);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return NoDataFound(message: context.translation.noDataFound).center;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget informationCard(AlertModel alertModel) {
|
||||
String zonesText = (alertModel.zones ?? [])
|
||||
.map((z) => z.zoneName ?? (z.zoneId != null ? 'Zone ${z.zoneId}' : null))
|
||||
.whereType<String>()
|
||||
.join(', ');
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
StatusLabel(
|
||||
label: alertModel.statusLabel,
|
||||
textColor: AppColor.getRequestStatusTextColorByName(context, alertModel.statusLabel),
|
||||
backgroundColor: AppColor.getRequestStatusColorByName(context, alertModel.statusLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
8.height,
|
||||
InfoHeader16Widget("Alert Details"),
|
||||
InfoTextWidget(label: "Alert No", value: alertModel.alertId ?? '-', copyValue: true),
|
||||
InfoTextWidget(label: "Rule Name", value: alertModel.ruleName ?? '-'),
|
||||
InfoTextWidget(label: "Trigger Type", value: alertModel.ruleTriggerType ?? '-'),
|
||||
InfoTextWidget(label: "Severity", value: alertModel.severity ?? '-'),
|
||||
if (alertModel.enter != null) InfoTextWidget(label: "Direction", value: alertModel.enter == true ? "Enter" : "Exit"),
|
||||
const Divider().defaultStyle(context),
|
||||
InfoHeader16Widget("Location"),
|
||||
InfoTextWidget(label: context.translation.site, value: alertModel.campusName ?? '-'),
|
||||
if (alertModel.venueName?.isNotEmpty ?? false) InfoTextWidget(label: "Venue", value: alertModel.venueName ?? '-'),
|
||||
if (alertModel.floorName?.isNotEmpty ?? false) InfoTextWidget(label: context.translation.floor, value: alertModel.floorName ?? '-'),
|
||||
if (zonesText.isNotEmpty) InfoTextWidget(label: "Zones", value: zonesText),
|
||||
],
|
||||
).expanded,
|
||||
InfoDateWidget(alertModel.createdDate ?? alertModel.dateTime ?? ""),
|
||||
],
|
||||
),
|
||||
],
|
||||
).toShadowContainer(context, padding: 16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:test_sa/extensions/context_extension.dart';
|
||||
import 'package:test_sa/extensions/int_extensions.dart';
|
||||
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_detail_page.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_model.dart';
|
||||
import 'package:test_sa/new_views/app_style/app_color.dart';
|
||||
import 'package:test_sa/views/widgets/item_views/info_date_widget.dart';
|
||||
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
|
||||
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
|
||||
import 'package:test_sa/views/widgets/requests/request_status.dart';
|
||||
|
||||
class AlertItemView extends StatelessWidget {
|
||||
final AlertModel alert;
|
||||
final bool showShadow;
|
||||
|
||||
const AlertItemView({Key? key, required this.alert, this.showShadow = true}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatusLabel(
|
||||
label: alert.statusLabel,
|
||||
textColor: AppColor.getRequestStatusTextColorByName(context, alert.statusLabel),
|
||||
backgroundColor: AppColor.getRequestStatusColorByName(context, alert.statusLabel),
|
||||
),
|
||||
1.width.expanded,
|
||||
InfoDateWidget(alert.createdDate ?? alert.dateTime ?? ""),
|
||||
],
|
||||
),
|
||||
InfoHeaderWidget(alert.ruleName ?? "Alert"),
|
||||
InfoTextWidget(label: "Severity", value: alert.severity),
|
||||
InfoTextWidget(label: context.translation.site, value: alert.campusName),
|
||||
InfoTextWidget(label: "Alert No", value: alert.alertId, showArrow: true),
|
||||
],
|
||||
).toShadowContainer(context, withShadow: showShadow).onPress(() {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => AlertDetailPage(alertId: alert.id!),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,132 @@
|
||||
class AlertZone {
|
||||
int? zoneId;
|
||||
String? zoneName;
|
||||
|
||||
AlertZone({this.zoneId, this.zoneName});
|
||||
|
||||
AlertZone.fromJson(Map<String, dynamic> json) {
|
||||
zoneId = json['zoneId'];
|
||||
zoneName = json['zoneName'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['zoneId'] = zoneId;
|
||||
data['zoneName'] = zoneName;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class AlertModel {
|
||||
int? id;
|
||||
int? ruleId;
|
||||
String? alertId;
|
||||
String? ruleName;
|
||||
String? ruleTriggerType;
|
||||
String? severity;
|
||||
String? dateTime;
|
||||
bool? resolved;
|
||||
bool? enter;
|
||||
int? entityId;
|
||||
int? campusId;
|
||||
String? campusName;
|
||||
int? venueId;
|
||||
String? venueName;
|
||||
int? floorId;
|
||||
String? floorName;
|
||||
List<AlertZone>? zones;
|
||||
int? geofenceId;
|
||||
dynamic idleThreshold;
|
||||
dynamic count;
|
||||
dynamic externalJson;
|
||||
String? createdDate;
|
||||
|
||||
AlertModel({
|
||||
this.id,
|
||||
this.ruleId,
|
||||
this.alertId,
|
||||
this.ruleName,
|
||||
this.ruleTriggerType,
|
||||
this.severity,
|
||||
this.dateTime,
|
||||
this.resolved,
|
||||
this.enter,
|
||||
this.entityId,
|
||||
this.campusId,
|
||||
this.campusName,
|
||||
this.venueId,
|
||||
this.venueName,
|
||||
this.floorId,
|
||||
this.floorName,
|
||||
this.zones,
|
||||
this.geofenceId,
|
||||
this.idleThreshold,
|
||||
this.count,
|
||||
this.externalJson,
|
||||
this.createdDate,
|
||||
});
|
||||
|
||||
AlertModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
ruleId = json['ruleId'];
|
||||
alertId = json['alertId'];
|
||||
ruleName = json['ruleName'];
|
||||
ruleTriggerType = json['ruleTriggerType'];
|
||||
severity = json['severity'];
|
||||
dateTime = json['dateTime'];
|
||||
resolved = json['resolved'];
|
||||
enter = json['enter'];
|
||||
entityId = json['entityId'];
|
||||
campusId = json['campusId'];
|
||||
campusName = json['campusName'];
|
||||
venueId = json['venueId'];
|
||||
venueName = json['venueName'];
|
||||
floorId = json['floorId'];
|
||||
floorName = json['floorName'];
|
||||
geofenceId = json['geofenceId'];
|
||||
idleThreshold = json['idleThreshold'];
|
||||
count = json['count'];
|
||||
externalJson = json['externalJson'];
|
||||
createdDate = json['createdDate'];
|
||||
if (json['zones'] != null) {
|
||||
zones = <AlertZone>[];
|
||||
(json['zones'] as List).forEach((v) {
|
||||
zones!.add(AlertZone.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['ruleId'] = ruleId;
|
||||
data['alertId'] = alertId;
|
||||
data['ruleName'] = ruleName;
|
||||
data['ruleTriggerType'] = ruleTriggerType;
|
||||
data['severity'] = severity;
|
||||
data['dateTime'] = dateTime;
|
||||
data['resolved'] = resolved;
|
||||
data['enter'] = enter;
|
||||
data['entityId'] = entityId;
|
||||
data['campusId'] = campusId;
|
||||
data['campusName'] = campusName;
|
||||
data['venueId'] = venueId;
|
||||
data['venueName'] = venueName;
|
||||
data['floorId'] = floorId;
|
||||
data['floorName'] = floorName;
|
||||
data['geofenceId'] = geofenceId;
|
||||
data['idleThreshold'] = idleThreshold;
|
||||
data['count'] = count;
|
||||
data['externalJson'] = externalJson;
|
||||
data['createdDate'] = createdDate;
|
||||
if (zones != null) {
|
||||
data['zones'] = zones!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bool get isResolved => resolved == true;
|
||||
|
||||
String get statusLabel => isResolved ? "Resolved" : "Open";
|
||||
}
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:test_sa/controllers/api_routes/api_manager.dart';
|
||||
import 'package:test_sa/controllers/api_routes/urls.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_model.dart';
|
||||
|
||||
class AlertProvider extends ChangeNotifier {
|
||||
final pageItemNumber = 12;
|
||||
|
||||
void reset() {
|
||||
items = null;
|
||||
nextPage = true;
|
||||
stateCode = null;
|
||||
}
|
||||
|
||||
int? stateCode;
|
||||
|
||||
bool nextPage = true;
|
||||
|
||||
List<AlertModel>? items;
|
||||
|
||||
bool isLoading = false;
|
||||
|
||||
AlertModel? alertModel;
|
||||
|
||||
Future<int> getAlerts() async {
|
||||
if (isLoading == true) return -2;
|
||||
isLoading = true;
|
||||
if (items?.isEmpty ?? true) notifyListeners();
|
||||
late Response response;
|
||||
|
||||
Map<String, dynamic> body = {};
|
||||
body["pageNumber"] = (items?.length ?? 0) ~/ pageItemNumber + 1;
|
||||
body["pageSize"] = pageItemNumber;
|
||||
|
||||
try {
|
||||
response = await ApiManager.instance.post(URLs.getAlerts, body: body);
|
||||
stateCode = response.statusCode;
|
||||
if (stateCode! >= 200 && stateCode! < 300) {
|
||||
final decoded = json.decode(response.body);
|
||||
final List requestsListJson = (decoded is Map && decoded.containsKey('data')) ? decoded['data'] : decoded;
|
||||
List<AlertModel> itemsPage = requestsListJson.map((request) => AlertModel.fromJson(request)).toList();
|
||||
items ??= [];
|
||||
items!.addAll(itemsPage);
|
||||
nextPage = itemsPage.length == pageItemNumber;
|
||||
}
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
return response.statusCode;
|
||||
} catch (error) {
|
||||
isLoading = false;
|
||||
stateCode = -1;
|
||||
notifyListeners();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AlertModel?> getAlertById(num id, {bool refresh = false}) async {
|
||||
if (refresh) {
|
||||
return alertModel;
|
||||
}
|
||||
try {
|
||||
alertModel = null;
|
||||
Response response = await ApiManager.instance.get("${URLs.getAlertById}/$id");
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
final decoded = json.decode(response.body);
|
||||
final Map<String, dynamic> dataNode = (decoded is Map && decoded.containsKey('data')) ? decoded['data'] : decoded;
|
||||
alertModel = AlertModel.fromJson(dataNode);
|
||||
notifyListeners();
|
||||
return alertModel;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> resolveAlert(num id) async {
|
||||
try {
|
||||
Response response = await ApiManager.instance.post(URLs.resolveAlert, body: {"id": id});
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
if (items != null) {
|
||||
reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:test_sa/extensions/context_extension.dart';
|
||||
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_item_view.dart';
|
||||
import 'package:test_sa/modules/alerts/alert_provider.dart';
|
||||
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
|
||||
import 'package:test_sa/views/widgets/loaders/lazy_loading.dart';
|
||||
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
|
||||
|
||||
class AlertsListPage extends StatefulWidget {
|
||||
final bool fromBottomBar;
|
||||
|
||||
const AlertsListPage({Key? key, this.fromBottomBar = false}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AlertsListPage> createState() => _AlertsListPageState();
|
||||
}
|
||||
|
||||
class _AlertsListPageState extends State<AlertsListPage> {
|
||||
AlertProvider? _provider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_provider = Provider.of<AlertProvider>(context);
|
||||
if (_provider!.items == null && !_provider!.isLoading) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_provider!.getAlerts();
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: DefaultAppBar(title: "Alerts", showBackButton: !widget.fromBottomBar),
|
||||
body: Consumer<AlertProvider>(builder: (context, snapshot, _) {
|
||||
final list = snapshot.items ?? [];
|
||||
if (list.isEmpty && !snapshot.isLoading) {
|
||||
return NoDataFound(message: context.translation.noDataFound).center;
|
||||
}
|
||||
return LazyLoading(
|
||||
nextPage: snapshot.nextPage,
|
||||
onLazyLoad: () async {
|
||||
await snapshot.getAlerts();
|
||||
},
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemBuilder: (cxt, index) {
|
||||
if (index >= list.length) return const SizedBox();
|
||||
return AlertItemView(alert: list[index]);
|
||||
},
|
||||
separatorBuilder: (cxt, index) => const SizedBox(height: 12),
|
||||
itemCount: list.length,
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue