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

View File

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

View File

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

View File

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

View File

@ -31,173 +31,178 @@ class AssignTagsTable extends StatelessWidget {
DataColumn _buildDataColumn(BuildContext context, String label) {
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
Widget build(BuildContext context) {
return BlocProvider<TagsBloc>(
create: (BuildContext context) => TagsBloc(
create: (context) => TagsBloc(
RemoteTagsService(HTTPService()),
)..add(const LoadTags()),
child: BlocBuilder<TagsBloc, TagsState>(
builder: (context, state) {
return switch (state) {
TagsLoading() || TagsInitial() => const Center(
child: CircularProgressIndicator(),
),
TagsFailure(:final message) => Center(
child: Text(message),
),
TagsLoaded(:final tags) => ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
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);
},
)),
),
],
);
}),
builder: (context, state) => switch (state) {
TagsLoading() || TagsInitial() => const Center(
child: CircularProgressIndicator(),
),
TagsFailure(:final message) => Center(
child: Text(message),
),
TagsLoaded(:final tags) => ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
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(
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 minBlueDot = Color(0xFF023DFE);
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 red100 = Color(0xFFFE0202);
static const Color grey800 = Color(0xffF8F8F8);
@ -93,4 +95,5 @@ abstract class ColorsManager {
static const Color shadowOfDetailsContainer = Color(0x40000000);
static const Color checkBoxBorderGray = Color(0xffD0D0D0);
static const Color timePickerColor = Color(0xff000000);
}