Progress towards 1493.

This commit is contained in:
Faris Armoush
2025-04-29 17:00:06 +03:00
parent c114161357
commit 1923ac7014
15 changed files with 404 additions and 31 deletions
@@ -0,0 +1,19 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
part 'analytics_tab_event.dart';
part 'analytics_tab_state.dart';
class AnalyticsTabBloc extends Bloc<AnalyticsTabEvent, AnalyticsTabState> {
AnalyticsTabBloc() : super(const AnalyticsTabState()) {
on<UpdateAnalyticsTabEvent>(_onUpdateAnalyticsTabEvent);
}
void _onUpdateAnalyticsTabEvent(
UpdateAnalyticsTabEvent event,
Emitter<AnalyticsTabState> emit,
) {
emit(state.copyWith(selectedTab: event.analyticsTab));
}
}
@@ -0,0 +1,17 @@
part of 'analytics_tab_bloc.dart';
sealed class AnalyticsTabEvent extends Equatable {
const AnalyticsTabEvent();
@override
List<Object> get props => [];
}
class UpdateAnalyticsTabEvent extends AnalyticsTabEvent {
const UpdateAnalyticsTabEvent(this.analyticsTab);
final AnalyticsPageTab analyticsTab;
@override
List<Object> get props => [analyticsTab];
}
@@ -0,0 +1,20 @@
part of 'analytics_tab_bloc.dart';
final class AnalyticsTabState extends Equatable {
const AnalyticsTabState({
this.selectedTab = AnalyticsPageTab.energyManagement,
});
final AnalyticsPageTab selectedTab;
AnalyticsTabState copyWith({
AnalyticsPageTab? selectedTab,
}) {
return AnalyticsTabState(
selectedTab: selectedTab ?? this.selectedTab,
);
}
@override
List<Object> get props => [selectedTab];
}
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
import 'package:syncrow_web/pages/analytics/modules/overview/views/analytics_overview_view.dart';
enum AnalyticsPageTab {
overview(
title: 'Overview',
child: AnalyticsOverviewView(),
),
energyManagement(
title: 'Energy Management',
child: AnalyticsEnergyManagementView(),
),
occupancy(
title: 'Occupancy',
child: AnalyticsOccupancyView(),
);
const AnalyticsPageTab({
required this.title,
required this.child,
});
final Widget child;
final String title;
}
@@ -1,19 +1,41 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AnalyticsPage extends StatelessWidget {
const AnalyticsPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<AnalyticsTabBloc>(
create: (context) => AnalyticsTabBloc(),
child: const AnalyticsPageForm(),
);
}
}
class AnalyticsPageForm extends StatelessWidget {
const AnalyticsPageForm({super.key});
@override
Widget build(BuildContext context) {
return WebScaffold(
rightBody: const NavigateHomeGridView(),
appBarTitle: Text(
'Syncrow Analytics',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
scaffoldBody: const Center(
child: Text('AnalyticsPage is Working!'),
enableMenuSidebar: false,
scaffoldBody: const Row(
children: [
AnalyticsCommunitiesSidebar(),
Expanded(flex: 5, child: AnalyticsPageTabsAndChildren()),
],
),
);
}
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
class AnalyticsCommunitiesSidebar extends StatelessWidget {
const AnalyticsCommunitiesSidebar({super.key});
@override
Widget build(BuildContext context) {
return Expanded(
child: SpaceTreeView(
title: const Text('Communities'),
shouldDisableDeselectingChildrenOfSelectedParent: true,
onSelect: () {},
isSide: false,
),
);
}
}
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AnalyticsDateFilterButton extends StatelessWidget {
const AnalyticsDateFilterButton({super.key});
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
@override
Widget build(BuildContext context) {
return TextButton.icon(
style: TextButton.styleFrom(
foregroundColor: _color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.greyColor,
width: 1,
),
),
backgroundColor: ColorsManager.transparentColor,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
icon: SvgPicture.asset(
Assets.blankCalendar,
height: 20,
width: 20,
colorFilter: ColorFilter.mode(_color, BlendMode.srcIn),
),
label: Text(
_concatenateDate(DateTime(2024, 1), DateTime(2024, 12)),
style: const TextStyle(
fontWeight: FontWeight.w700,
),
),
onPressed: () {},
);
}
String _formatDate(DateTime date) {
final formatter = DateFormat('MMM yyyy');
final formattedDate = formatter.format(date);
return formattedDate;
}
String _concatenateDate(DateTime startDate, DateTime endDate) {
final formattedStartDate = _formatDate(startDate);
final formattedEndDate = _formatDate(endDate);
return '$formattedStartDate - $formattedEndDate';
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AnalyticsPageTabButton extends StatelessWidget {
const AnalyticsPageTabButton({
super.key,
required this.tab,
required this.isSelected,
});
final AnalyticsPageTab tab;
final bool isSelected;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () => context.read<AnalyticsTabBloc>().add(
UpdateAnalyticsTabEvent(tab),
),
child: Text(
tab.title,
textAlign: TextAlign.center,
maxLines: 1,
softWrap: false,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400,
fontSize: 16,
color:
isSelected ? ColorsManager.slidingBlueColor : ColorsManager.textGray,
),
),
);
}
}
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
import 'package:syncrow_web/utils/style.dart';
class AnalyticsPageTabsAndChildren extends StatelessWidget {
const AnalyticsPageTabsAndChildren({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<AnalyticsTabBloc, AnalyticsTabState>(
buildWhen: (previous, current) => previous.selectedTab != current.selectedTab,
builder: (context, state) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Container(
width: MediaQuery.sizeOf(context).width * 1,
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.symmetric(
horizontal: 32,
// vertical: 48,
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...AnalyticsPageTab.values.map(
(tab) => Expanded(
child: AnalyticsPageTabButton(
tab: tab,
isSelected: tab == state.selectedTab,
),
),
),
const Expanded(
flex: 2,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDateFilterButton(),
),
),
],
),
),
),
Expanded(
flex: 8,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: AnimatedSwitcher(
switchInCurve: Curves.easeIn,
duration: const Duration(milliseconds: 200),
child: state.selectedTab.child,
),
),
),
],
),
);
}
}
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsEnergyManagementView extends StatelessWidget {
const AnalyticsEnergyManagementView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('EnergyManagementView is Working!'),
);
}
}
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsOccupancyView extends StatelessWidget {
const AnalyticsOccupancyView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('AnalyticsOccupancyView is Working!'),
);
}
}
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsOverviewView extends StatelessWidget {
const AnalyticsOverviewView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Coming Soon!'),
);
}
}
+76 -29
View File
@@ -17,7 +17,15 @@ import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
final bool? isSide;
final Function onSelect;
const SpaceTreeView({required this.onSelect, this.isSide, super.key});
final bool shouldDisableDeselectingChildrenOfSelectedParent;
final Widget? title;
const SpaceTreeView({
required this.onSelect,
this.isSide,
super.key,
this.shouldDisableDeselectingChildrenOfSelectedParent = false,
this.title,
});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
@@ -41,17 +49,31 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
final communities =
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity
: state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: widget.isSide == true
? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors)
? subSectionContainerDecoration.copyWith(
color: ColorsManager.whiteColors)
: const BoxDecoration(color: ColorsManager.whiteColors),
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
if (widget.title != null)
Container(
alignment: AlignmentDirectional.centerStart,
padding: const EdgeInsets.all(24),
child: DefaultTextStyle(
style: context.textTheme.titleMedium!.copyWith(
color: ColorsManager.blackColor,
fontSize: 20,
),
child: widget.title!,
),
),
if (widget.isSide == true)
Container(
decoration: const BoxDecoration(
@@ -79,10 +101,12 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.blackColor,
),
onChanged: (value) => context.read<SpaceTreeBloc>().add(
SearchQueryEvent(value),
),
decoration: textBoxDecoration(radios: 20)?.copyWith(
onChanged: (value) =>
context.read<SpaceTreeBloc>().add(
SearchQueryEvent(value),
),
decoration:
textBoxDecoration(radios: 20)?.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 16),
@@ -92,7 +116,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium?.copyWith(
hintStyle:
context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray,
@@ -131,15 +156,16 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: communities[index].name,
isSelected:
state.selectedCommunities.contains(communities[index].uuid),
isSoldCheck:
state.selectedCommunities.contains(communities[index].uuid),
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(
communities[index].uuid,
),
),
isSelected: state.selectedCommunities
.contains(communities[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(communities[index].uuid),
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(
communities[index].uuid,
),
),
isExpanded: state.expandedCommunities.contains(
communities[index].uuid,
),
@@ -156,8 +182,19 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces.contains(space.uuid),
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
final isParentSelected = _isParentSelected(
state,
communities[index],
space,
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
communities[index],
@@ -167,15 +204,18 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
);
widget.onSelect();
},
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces.contains(space.uuid) ||
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context,
state,
@@ -196,6 +236,13 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
});
}
bool _isParentSelected(
SpaceTreeState state, CommunityModel community, SpaceModel space) {
return state.selectedCommunities.contains(community.uuid) ||
(space.spaceModel?.uuid != null &&
state.selectedSpaces.contains(space.spaceModel?.uuid));
}
List<Widget> _buildNestedSpaces(
BuildContext context,
SpaceTreeState state,
@@ -204,8 +251,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSelected: state.selectedSpaces.contains(child.uuid) ||
state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),
+1
View File
@@ -480,4 +480,5 @@ class Assets {
static const String DisappeDelayIcon = 'assets/icons/disappe_delay_icon.svg';
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
}