mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-26 08:59:41 +00:00
Compare commits
34 Commits
7f3dfebf15
...
fix-bookin
Author | SHA1 | Date | |
---|---|---|---|
5a35f8e62e | |||
04d1c37308 | |||
dfdf4fb27c | |||
3859dc67d8 | |||
ae3eb6fca8 | |||
f341dcd482 | |||
e98b091253 | |||
77d6d822cb | |||
f1aab13263 | |||
a56e422bf5 | |||
97530dd351 | |||
a57b6e0853 | |||
8f71fcb96a | |||
845397e819 | |||
2077ef053f | |||
d21850edc8 | |||
85f53ed1f2 | |||
5fde74fc7d | |||
994efc302b | |||
c403048da7 | |||
04b7a506be | |||
19ddf443a9 | |||
3779176978 | |||
7c5bca35fc | |||
aed3004a31 | |||
8ae4e561c2 | |||
4241d11cb6 | |||
ef8c9efff0 | |||
c59d2b7fd6 | |||
71f0da9299 | |||
e6d9000ee2 | |||
7dc103f904 | |||
e4c41bab90 | |||
59058cf2d2 |
@ -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';
|
||||
@ -20,7 +19,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class BookingPage extends StatefulWidget {
|
||||
const BookingPage({super.key});
|
||||
final PageController? pageController;
|
||||
const BookingPage({super.key, this.pageController});
|
||||
|
||||
@override
|
||||
State<BookingPage> createState() => _BookingPageState();
|
||||
@ -42,12 +42,15 @@ class _BookingPageState extends State<BookingPage> {
|
||||
),
|
||||
),
|
||||
],
|
||||
child: _BookingPageContent(),
|
||||
child: _BookingPageContent(widget.pageController),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BookingPageContent extends StatefulWidget {
|
||||
final PageController? pageController;
|
||||
const _BookingPageContent(this.pageController);
|
||||
|
||||
@override
|
||||
State<_BookingPageContent> createState() => _BookingPageContentState();
|
||||
}
|
||||
@ -90,8 +93,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);
|
||||
}
|
||||
},
|
||||
@ -176,7 +178,9 @@ class _BookingPageContentState extends State<_BookingPageContent> {
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
widget.pageController!.jumpToPage(2);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
@ -237,7 +241,6 @@ class _BookingPageContentState extends State<_BookingPageContent> {
|
||||
.watch<DateSelectionBloc>()
|
||||
.state
|
||||
.selectedDateFromSideBarCalender,
|
||||
// isLoading: eventState is EventsLoading,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -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),
|
||||
|
@ -1,57 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class BookingPage extends StatelessWidget {
|
||||
final PageController pageController;
|
||||
const BookingPage({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Colors.blueGrey[100],
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Side bar',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: SizedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(2);
|
||||
}),
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
onPressed: () {})
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
|
||||
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/booking_system/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart' hide BookingPage;
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||
@ -51,13 +53,23 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
duration: const Duration(milliseconds: 150),
|
||||
);
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_centerOnTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.selectedSpace == null) return;
|
||||
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||
if (oldWidget.community.uuid != widget.community.uuid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_centerOnTree(animate: true);
|
||||
}
|
||||
});
|
||||
} else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_animateToSpace(widget.selectedSpace);
|
||||
@ -151,6 +163,60 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
_runAnimation(matrix);
|
||||
}
|
||||
|
||||
void _centerOnTree({bool animate = false}) {
|
||||
if (_positions.isEmpty) {
|
||||
if (animate) {
|
||||
_runAnimation(Matrix4.identity());
|
||||
} else {
|
||||
_transformationController.value = Matrix4.identity();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var minX = double.infinity;
|
||||
var maxX = double.negativeInfinity;
|
||||
var minY = double.infinity;
|
||||
var maxY = double.negativeInfinity;
|
||||
|
||||
_positions.forEach((uuid, offset) {
|
||||
final cardWidth = _cardWidths[uuid] ?? _minCardWidth;
|
||||
minX = min(minX, offset.dx);
|
||||
maxX = max(maxX, offset.dx + cardWidth);
|
||||
minY = min(minY, offset.dy);
|
||||
maxY = max(maxY, offset.dy + _cardHeight);
|
||||
});
|
||||
|
||||
if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) {
|
||||
return;
|
||||
}
|
||||
|
||||
final treeWidth = maxX - minX;
|
||||
final treeHeight = maxY - minY;
|
||||
|
||||
final viewSize = context.size;
|
||||
if (viewSize == null) return;
|
||||
|
||||
final scaleX = viewSize.width / treeWidth;
|
||||
final scaleY = viewSize.height / treeHeight;
|
||||
final scale = min(scaleX, scaleY).clamp(0.5, 1.0) * 0.9;
|
||||
|
||||
final treeCenterX = minX + treeWidth / 2;
|
||||
final treeCenterY = minY + treeHeight / 2;
|
||||
|
||||
final x = -treeCenterX * scale + viewSize.width / 2;
|
||||
final y = -treeCenterY * scale + viewSize.height / 2;
|
||||
|
||||
final matrix = Matrix4.identity()
|
||||
..translate(x, y)
|
||||
..scale(scale);
|
||||
|
||||
if (animate) {
|
||||
_runAnimation(matrix);
|
||||
} else {
|
||||
_transformationController.value = matrix;
|
||||
}
|
||||
}
|
||||
|
||||
void _onReorder(SpaceReorderDataModel data, int newIndex) {
|
||||
final newCommunity = widget.community.copyWith();
|
||||
final children = data.parent?.children ?? newCommunity.spaces;
|
||||
@ -508,8 +574,8 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
horizontal: context.screenWidth * 0.3,
|
||||
vertical: context.screenHeight * 0.3,
|
||||
),
|
||||
minScale: 0.5,
|
||||
maxScale: 3.0,
|
||||
minScale: 0.3,
|
||||
maxScale: 4.5,
|
||||
constrained: false,
|
||||
child: SizedBox(
|
||||
width: context.screenWidth * 5,
|
||||
|
@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
|
||||
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||
@ -44,7 +45,22 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
onDuplicate: (space) {},
|
||||
onDuplicate: (space) => showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => DuplicateSpaceDialog(
|
||||
initialName: space.spaceName,
|
||||
selectedSpaceUuid: space.uuid,
|
||||
selectedCommunityUuid: selectedCommunity.uuid,
|
||||
onSuccess: (spaces) {
|
||||
final updatedCommunity = selectedCommunity.copyWith(
|
||||
spaces: spaces,
|
||||
);
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(updatedCommunity),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||
context,
|
||||
spaceModel: selectedSpace!,
|
||||
|
@ -10,21 +10,26 @@ class SpaceManagementBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
return Stack(
|
||||
children: [
|
||||
const SpaceManagementCommunitiesTree(),
|
||||
Expanded(
|
||||
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
||||
CommunitiesTreeSelectionState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.selectedCommunity != current.selectedCommunity,
|
||||
builder: (context, state) => Visibility(
|
||||
visible: state.selectedCommunity == null,
|
||||
replacement: const SpaceManagementCommunityStructure(),
|
||||
child: const SpaceManagementTemplatesView(),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 320),
|
||||
Expanded(
|
||||
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
||||
CommunitiesTreeSelectionState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.selectedCommunity != current.selectedCommunity,
|
||||
builder: (context, state) => Visibility(
|
||||
visible: state.selectedCommunity == null,
|
||||
replacement: const SpaceManagementCommunityStructure(),
|
||||
child: const SpaceManagementTemplatesView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SpaceManagementCommunitiesTree(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class SpaceManagementCommunitiesTree extends StatefulWidget {
|
||||
@ -44,7 +45,15 @@ class _SpaceManagementCommunitiesTreeState
|
||||
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
|
||||
builder: (context, state) => Container(
|
||||
width: 320,
|
||||
decoration: subSectionContainerDecoration,
|
||||
decoration: subSectionContainerDecoration.copyWith(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(10, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SpaceManagementSidebarHeader(),
|
||||
|
@ -0,0 +1,60 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
final class RemoteDuplicateSpaceService implements DuplicateSpaceService {
|
||||
RemoteDuplicateSpaceService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param) async {
|
||||
try {
|
||||
final response = await _httpService.post(
|
||||
path: await _makeUrl(param),
|
||||
body: param.toJson(),
|
||||
expectedResponseModel: (json) {
|
||||
final response = json as Map<String, dynamic>;
|
||||
final data = response['data'] as List<dynamic>;
|
||||
return data
|
||||
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
throw APIException(errorMessage);
|
||||
} catch (e) {
|
||||
throw APIException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _makeUrl(DuplicateSpaceParam param) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID();
|
||||
if (projectUuid == null) {
|
||||
throw APIException('Project UUID is not set');
|
||||
}
|
||||
|
||||
if (param.communityUuid.isEmpty) {
|
||||
throw APIException('Community UUID is not set');
|
||||
}
|
||||
|
||||
if (param.spaceUuid.isEmpty) {
|
||||
throw APIException('Space UUID is not set');
|
||||
}
|
||||
|
||||
return ApiEndpoints.duplicateSpace
|
||||
.replaceAll('{projectUuid}', projectUuid)
|
||||
.replaceAll('{communityUuid}', param.communityUuid)
|
||||
.replaceAll('{spaceUuid}', param.spaceUuid);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
class DuplicateSpaceParam {
|
||||
final String communityUuid;
|
||||
final String spaceUuid;
|
||||
final String newSpaceName;
|
||||
|
||||
DuplicateSpaceParam({
|
||||
required this.communityUuid,
|
||||
required this.spaceUuid,
|
||||
required this.newSpaceName,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'spaceName': newSpaceName,
|
||||
};
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
|
||||
abstract interface class DuplicateSpaceService {
|
||||
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'duplicate_space_event.dart';
|
||||
part 'duplicate_space_state.dart';
|
||||
|
||||
class DuplicateSpaceBloc extends Bloc<DuplicateSpaceEvent, DuplicateSpaceState> {
|
||||
DuplicateSpaceBloc(
|
||||
this._duplicateSpaceService,
|
||||
) : super(const DuplicateSpaceInitial()) {
|
||||
on<DuplicateSpaceEvent>(_onDuplicateSpaceEvent);
|
||||
}
|
||||
|
||||
final DuplicateSpaceService _duplicateSpaceService;
|
||||
|
||||
Future<void> _onDuplicateSpaceEvent(
|
||||
DuplicateSpaceEvent event,
|
||||
Emitter<DuplicateSpaceState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const DuplicateSpaceLoading());
|
||||
final result = await _duplicateSpaceService.duplicateSpace(event.param);
|
||||
emit(DuplicateSpaceSuccess(result));
|
||||
} on APIException catch (e) {
|
||||
emit(DuplicateSpaceFailure(e.message));
|
||||
} catch (e) {
|
||||
emit(DuplicateSpaceFailure(e.toString()));
|
||||
} finally {
|
||||
emit(const DuplicateSpaceInitial());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
part of 'duplicate_space_bloc.dart';
|
||||
|
||||
final class DuplicateSpaceEvent extends Equatable {
|
||||
const DuplicateSpaceEvent({required this.param});
|
||||
|
||||
final DuplicateSpaceParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
part of 'duplicate_space_bloc.dart';
|
||||
|
||||
sealed class DuplicateSpaceState extends Equatable {
|
||||
const DuplicateSpaceState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class DuplicateSpaceInitial extends DuplicateSpaceState {
|
||||
const DuplicateSpaceInitial();
|
||||
}
|
||||
|
||||
final class DuplicateSpaceLoading extends DuplicateSpaceState {
|
||||
const DuplicateSpaceLoading();
|
||||
}
|
||||
|
||||
final class DuplicateSpaceSuccess extends DuplicateSpaceState {
|
||||
const DuplicateSpaceSuccess(this.spaces);
|
||||
|
||||
final List<SpaceModel> spaces;
|
||||
|
||||
@override
|
||||
List<Object> get props => [spaces];
|
||||
}
|
||||
|
||||
final class DuplicateSpaceFailure extends DuplicateSpaceState {
|
||||
const DuplicateSpaceFailure(this.errorMessage);
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/widgets/app_loading_indicator.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/extension/app_snack_bar.dart';
|
||||
|
||||
class DuplicateSpaceDialog extends StatelessWidget {
|
||||
const DuplicateSpaceDialog({
|
||||
required this.initialName,
|
||||
required this.onSuccess,
|
||||
required this.selectedSpaceUuid,
|
||||
required this.selectedCommunityUuid,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String initialName;
|
||||
final void Function(List<SpaceModel> spaces) onSuccess;
|
||||
final String selectedSpaceUuid;
|
||||
final String selectedCommunityUuid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DuplicateSpaceBloc(
|
||||
RemoteDuplicateSpaceService(HTTPService()),
|
||||
),
|
||||
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
|
||||
listener: _listener,
|
||||
child: DuplicateSpaceDialogForm(
|
||||
initialName: initialName,
|
||||
selectedSpaceUuid: selectedSpaceUuid,
|
||||
selectedCommunityUuid: selectedCommunityUuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _listener(BuildContext context, DuplicateSpaceState state) {
|
||||
switch (state) {
|
||||
case DuplicateSpaceLoading():
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const AppLoadingIndicator(),
|
||||
);
|
||||
break;
|
||||
|
||||
case DuplicateSpaceFailure(:final errorMessage):
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
context.showFailureSnackbar(errorMessage);
|
||||
break;
|
||||
|
||||
case DuplicateSpaceSuccess(:final spaces):
|
||||
onSuccess.call(spaces);
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
context.showSuccessSnackbar('Space duplicated successfully');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart';
|
||||
|
||||
class DuplicateSpaceDialogForm extends StatefulWidget {
|
||||
const DuplicateSpaceDialogForm({
|
||||
required this.initialName,
|
||||
required this.selectedSpaceUuid,
|
||||
required this.selectedCommunityUuid,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String initialName;
|
||||
final String selectedSpaceUuid;
|
||||
final String selectedCommunityUuid;
|
||||
|
||||
@override
|
||||
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
|
||||
}
|
||||
|
||||
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
||||
late final TextEditingController _nameController;
|
||||
bool _isNameValid = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: '${widget.initialName}(1)');
|
||||
_nameController.addListener(_validateName);
|
||||
}
|
||||
|
||||
void _validateName() => setState(
|
||||
() => _isNameValid = _nameController.text.trim() != widget.initialName,
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const SelectableText('Duplicate Space'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 16,
|
||||
children: [
|
||||
const SelectableText('Enter a new name for the duplicated space:'),
|
||||
DuplicateSpaceTextField(
|
||||
nameController: _nameController,
|
||||
isNameValid: _isNameValid,
|
||||
initialName: widget.initialName,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isNameValid ? () => _submit(context) : null,
|
||||
child: const Text('Duplicate'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _submit(BuildContext context) {
|
||||
context.read<DuplicateSpaceBloc>().add(
|
||||
DuplicateSpaceEvent(
|
||||
param: DuplicateSpaceParam(
|
||||
newSpaceName: _nameController.text,
|
||||
spaceUuid: widget.selectedSpaceUuid,
|
||||
communityUuid: widget.selectedCommunityUuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DuplicateSpaceFailureDialog extends StatelessWidget {
|
||||
const DuplicateSpaceFailureDialog(this.errorMessage, {super.key});
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Failed to duplicate space'),
|
||||
content: Text(errorMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class DuplicateSpaceTextField extends StatelessWidget {
|
||||
const DuplicateSpaceTextField({
|
||||
required this.nameController,
|
||||
required this.isNameValid,
|
||||
required this.initialName,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TextEditingController nameController;
|
||||
final bool isNameValid;
|
||||
final String initialName;
|
||||
|
||||
String get _errorText => 'Name must be different from "$initialName"';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: nameController,
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
label: const Text('Space Name'),
|
||||
border: _border(),
|
||||
enabledBorder: _border(),
|
||||
focusedBorder: _border(ColorsManager.primaryColor),
|
||||
errorBorder: _border(context.theme.colorScheme.error),
|
||||
focusedErrorBorder: _border(context.theme.colorScheme.error),
|
||||
errorStyle: context.textTheme.bodyMedium!.copyWith(
|
||||
color: context.theme.colorScheme.error,
|
||||
fontSize: 8,
|
||||
),
|
||||
errorText: isNameValid ? null : _errorText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OutlineInputBorder _border([Color? color]) {
|
||||
return OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: color ?? ColorsManager.blackColor,
|
||||
width: 0.5,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ class SpaceSubSpacesDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
late List<Subspace> _subspaces;
|
||||
|
||||
bool get _hasDuplicateNames =>
|
||||
@ -29,6 +30,13 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaces = List.from(widget.subspaces);
|
||||
_subspaceNameController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleSubspaceAdded(String name) {
|
||||
@ -49,6 +57,10 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
);
|
||||
|
||||
void _handleSave() {
|
||||
final name = _subspaceNameController.text.trim();
|
||||
if (name.isNotEmpty) {
|
||||
_handleSubspaceAdded(name);
|
||||
}
|
||||
widget.onSave(_subspaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@ -65,6 +77,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
subSpaces: _subspaces,
|
||||
onSubspaceAdded: _handleSubspaceAdded,
|
||||
onSubspaceDeleted: _handleSubspaceDeleted,
|
||||
controller: _subspaceNameController,
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
|
@ -10,29 +10,28 @@ class SubSpacesInput extends StatefulWidget {
|
||||
required this.subSpaces,
|
||||
required this.onSubspaceAdded,
|
||||
required this.onSubspaceDeleted,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final List<Subspace> subSpaces;
|
||||
final void Function(String name) onSubspaceAdded;
|
||||
final void Function(String uuid) onSubspaceDeleted;
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
State<SubSpacesInput> createState() => _SubSpacesInputState();
|
||||
}
|
||||
|
||||
class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
late final FocusNode _focusNode;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaceNameController = TextEditingController();
|
||||
_focusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@ -81,7 +80,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
width: 200,
|
||||
child: TextField(
|
||||
focusNode: _focusNode,
|
||||
controller: _subspaceNameController,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
|
||||
@ -93,7 +92,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
widget.onSubspaceAdded(trimmedValue);
|
||||
_subspaceNameController.clear();
|
||||
widget.controller.clear();
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
|
@ -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(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -141,12 +141,12 @@ abstract class ApiEndpoints {
|
||||
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
|
||||
static const String saveSchedule = '/schedule/{deviceUuid}';
|
||||
|
||||
|
||||
static const String duplicateSpace = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/duplicate';
|
||||
|
||||
////booking System
|
||||
static const String bookableSpaces = '/bookable-spaces';
|
||||
static const String getCalendarEvents = '/api';
|
||||
static const String getBookings =
|
||||
'/bookings?month={mm}%2F{yyyy}&space={space}';
|
||||
'/bookings?month={mm}-{yyyy}&space={space}';
|
||||
|
||||
}
|
||||
|
56
lib/utils/extension/app_snack_bar.dart
Normal file
56
lib/utils/extension/app_snack_bar.dart
Normal file
@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
extension AppSnackBarsBuildContextExtension on BuildContext {
|
||||
void showSuccessSnackbar(String message) {
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
_makeSnackbar(
|
||||
message: message,
|
||||
icon: Icons.check_circle,
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showFailureSnackbar(String message) {
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
_makeSnackbar(
|
||||
message: message,
|
||||
icon: Icons.error,
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SnackBar _makeSnackbar({
|
||||
required String message,
|
||||
required Color backgroundColor,
|
||||
required IconData icon,
|
||||
}) {
|
||||
return SnackBar(
|
||||
content: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(icon, color: Colors.white),
|
||||
Text(
|
||||
message,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
margin: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 92,
|
||||
vertical: 32,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user