Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1722-FE-Implement-Duplicate-Space-Feature

This commit is contained in:
Faris Armoush
2025-07-23 14:19:55 +03:00
6 changed files with 192 additions and 170 deletions

View File

@ -1,4 +1,3 @@
// booking_page.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
@ -90,8 +89,7 @@ class _BookingPageContentState extends State<_BookingPageContent> {
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>( return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
listener: (context, state) { listener: (context, state) {
if (state.selectedBookableSpace != null) { if (state.selectedBookableSpace != null) {
// Reset events and clear cache when room changes context.read<CalendarEventsBloc>().add(const ResetEvents());
context.read<CalendarEventsBloc>().add(ResetEvents());
_loadEvents(context); _loadEvents(context);
} }
}, },
@ -237,7 +235,6 @@ class _BookingPageContentState extends State<_BookingPageContent> {
.watch<DateSelectionBloc>() .watch<DateSelectionBloc>()
.state .state
.selectedDateFromSideBarCalender, .selectedDateFromSideBarCalender,
// isLoading: eventState is EventsLoading,
); );
}, },
); );

View File

@ -19,6 +19,7 @@ class WeekNavigation extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: 250,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.circleRolesBackground, color: ColorsManager.circleRolesBackground,
@ -32,6 +33,8 @@ class WeekNavigation extends StatelessWidget {
], ],
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
iconSize: 15, iconSize: 15,
@ -40,12 +43,16 @@ class WeekNavigation extends StatelessWidget {
onPressed: onPreviousWeek, onPressed: onPreviousWeek,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( SizedBox(
_getMonthYearText(weekStart, weekEnd), width: 120,
style: const TextStyle( child: Text(
color: ColorsManager.lightGrayColor, _getMonthYearText(weekStart, weekEnd),
fontSize: 14, style: const TextStyle(
fontWeight: FontWeight.w400, color: ColorsManager.lightGrayColor,
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'space_tree_dropdown_bloc.dart'; import 'space_tree_dropdown_bloc.dart';
class DropdownMenuContent extends StatefulWidget { class DropdownMenuContent extends StatefulWidget {
@ -75,7 +76,8 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
child: TextFormField( child: TextFormField(
controller: _searchController, controller: _searchController,
onChanged: _handleSearch, onChanged: _handleSearch,
style: const TextStyle(fontSize: 14, color: Colors.black), style: const TextStyle(
fontSize: 14, color: ColorsManager.blackColor),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Search for space...', hintText: 'Search for space...',
prefixIcon: const Icon(Icons.search, size: 20), prefixIcon: const Icon(Icons.search, size: 20),
@ -84,6 +86,12 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
color: ColorsManager.dropDownSelectBlue,
),
),
isDense: true, isDense: true,
), ),
), ),
@ -117,7 +125,9 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
title: Text( title: Text(
community.name, community.name,
style: TextStyle( style: TextStyle(
color: isSelected ? Colors.blue : Colors.black, color: isSelected
? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor,
fontWeight: fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal, isSelected ? FontWeight.bold : FontWeight.normal,
), ),

View File

@ -52,7 +52,7 @@ class SpaceDropdown extends StatelessWidget {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: selectedValue == space.uuid color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle ? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor, : ColorsManager.blackColor,
), ),
), ),
@ -61,7 +61,7 @@ class SpaceDropdown extends StatelessWidget {
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12, fontSize: 12,
color: selectedValue == space.uuid color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle ? ColorsManager.dropDownSelectBlue
: ColorsManager.blackColor, : ColorsManager.blackColor,
), ),
), ),

View File

@ -31,173 +31,178 @@ class AssignTagsTable extends StatelessWidget {
DataColumn _buildDataColumn(BuildContext context, String label) { DataColumn _buildDataColumn(BuildContext context, String label) {
return DataColumn( return DataColumn(
label: SelectableText(label, style: context.textTheme.bodyMedium), label: Expanded(
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: SelectableText(label, style: context.textTheme.bodyMedium),
),
),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<TagsBloc>( return BlocProvider<TagsBloc>(
create: (BuildContext context) => TagsBloc( create: (context) => TagsBloc(
RemoteTagsService(HTTPService()), RemoteTagsService(HTTPService()),
)..add(const LoadTags()), )..add(const LoadTags()),
child: BlocBuilder<TagsBloc, TagsState>( child: BlocBuilder<TagsBloc, TagsState>(
builder: (context, state) { builder: (context, state) => switch (state) {
return switch (state) { TagsLoading() || TagsInitial() => const Center(
TagsLoading() || TagsInitial() => const Center( child: CircularProgressIndicator(),
child: CircularProgressIndicator(), ),
), TagsFailure(:final message) => Center(
TagsFailure(:final message) => Center( child: Text(message),
child: Text(message), ),
), TagsLoaded(:final tags) => ClipRRect(
TagsLoaded(:final tags) => ClipRRect( borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), child: DataTable(
child: DataTable( headingRowColor: WidgetStateProperty.all(
headingRowColor: WidgetStateProperty.all( ColorsManager.dataHeaderGrey,
ColorsManager.dataHeaderGrey,
),
key: ValueKey(productAllocations.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
_buildDataColumn(context, '#'),
_buildDataColumn(context, 'Device'),
_buildDataColumn(context, 'Tag'),
_buildDataColumn(context, 'Location'),
],
rows: productAllocations.isEmpty
? [
DataRow(
cells: [
DataCell(
Center(
child: SelectableText(
'No Devices Available',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
),
),
DataCell.empty,
DataCell.empty,
DataCell.empty,
],
),
]
: List.generate(productAllocations.length, (index) {
final productAllocation = productAllocations[index];
final allocationUuid = productAllocation.uuid;
final availableTags = tags
.where(
(tag) =>
!productAllocations
.where((p) =>
p.product.productType ==
productAllocation.product.productType)
.map((p) => p.tag.name.toLowerCase())
.contains(tag.name.toLowerCase()) ||
tag.uuid == productAllocation.tag.uuid,
)
.toList();
final currentLocationUuid =
productLocations[allocationUuid];
final currentLocationName = currentLocationUuid == null
? 'Main Space'
: subspaces
.firstWhere((s) => s.uuid == currentLocationUuid)
.name;
return DataRow(
key: ValueKey(allocationUuid),
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
productAllocation.product.name,
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
onProductDeleted(allocationUuid);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment.centerLeft,
width: double.infinity,
child: ProductTagField(
key: ValueKey('dropdown_$allocationUuid'),
productName: productAllocation.product.uuid,
initialValue: productAllocation.tag,
onSelected: (newTag) {
onTagSelected(allocationUuid, newTag);
},
items: availableTags,
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: [
'Main Space',
...subspaces.map((s) => s.name)
],
selectedValue: currentLocationName,
onSelected: (newLocationName) {
final newSubspaceUuid = newLocationName ==
'Main Space'
? null
: subspaces
.firstWhere(
(s) => s.name == newLocationName)
.uuid;
onLocationSelected(
allocationUuid, newSubspaceUuid);
},
)),
),
],
);
}),
), ),
key: ValueKey(productAllocations.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
_buildDataColumn(context, '#'),
_buildDataColumn(context, 'Device'),
_buildDataColumn(context, 'Tag'),
_buildDataColumn(context, 'Location'),
],
rows: productAllocations.isEmpty
? [
DataRow(
cells: [
DataCell(
FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: SelectableText(
'No Devices Available',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
),
),
DataCell.empty,
DataCell.empty,
DataCell.empty,
],
),
]
: List.generate(productAllocations.length, (index) {
final productAllocation = productAllocations[index];
final allocationUuid = productAllocation.uuid;
final availableTags = tags
.where(
(tag) =>
!productAllocations
.where((p) =>
p.product.productType ==
productAllocation.product.productType)
.map((p) => p.tag.name.toLowerCase())
.contains(tag.name.toLowerCase()) ||
tag.uuid == productAllocation.tag.uuid,
)
.toList();
final currentLocationUuid = productLocations[allocationUuid];
final currentLocationName = currentLocationUuid == null
? 'Main Space'
: subspaces
.firstWhere((s) => s.uuid == currentLocationUuid)
.name;
return DataRow(
key: ValueKey(allocationUuid),
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
productAllocation.product.name,
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
onProductDeleted(allocationUuid);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment.centerLeft,
width: double.infinity,
child: ProductTagField(
key: ValueKey('dropdown_$allocationUuid'),
productName: productAllocation.product.uuid,
initialValue: productAllocation.tag,
onSelected: (newTag) {
onTagSelected(allocationUuid, newTag);
},
items: availableTags,
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: [
'Main Space',
...subspaces.map((s) => s.name)
],
selectedValue: currentLocationName,
onSelected: (newLocationName) {
final newSubspaceUuid = newLocationName ==
'Main Space'
? null
: subspaces
.firstWhere(
(s) => s.name == newLocationName)
.uuid;
onLocationSelected(
allocationUuid, newSubspaceUuid);
},
)),
),
],
);
}),
), ),
_ => const SizedBox.shrink(), ),
}; _ => const SizedBox.shrink(),
}, },
), ),
); );

View File

@ -85,6 +85,8 @@ abstract class ColorsManager {
static const Color minBlue = Color(0xFF93AAFD); static const Color minBlue = Color(0xFF93AAFD);
static const Color minBlueDot = Color(0xFF023DFE); static const Color minBlueDot = Color(0xFF023DFE);
static const Color grey25 = Color(0xFFF9F9F9); static const Color grey25 = Color(0xFFF9F9F9);
static const Color dropDownSelectBlue = Color(0xFF2196F3);
static const Color drpoDownSelectBlue = Color(0xFF2196F3);
static const Color grey50 = Color(0xFF718096); static const Color grey50 = Color(0xFF718096);
static const Color red100 = Color(0xFFFE0202); static const Color red100 = Color(0xFFFE0202);
static const Color grey800 = Color(0xffF8F8F8); static const Color grey800 = Color(0xffF8F8F8);
@ -93,4 +95,5 @@ abstract class ColorsManager {
static const Color shadowOfDetailsContainer = Color(0x40000000); static const Color shadowOfDetailsContainer = Color(0x40000000);
static const Color checkBoxBorderGray = Color(0xffD0D0D0); static const Color checkBoxBorderGray = Color(0xffD0D0D0);
static const Color timePickerColor = Color(0xff000000); static const Color timePickerColor = Color(0xff000000);
} }