Compare commits

..

6 Commits

Author SHA1 Message Date
59058cf2d2 enhance week navigation layout for improved UI 2025-07-22 14:40:50 +03:00
0eb4652f26 Enhance garage door scheduling functionality and UI improvements (#358)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->
Enhance garage door scheduling functionality and UI improvements

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-21 16:25:53 +03:00
460639b681 Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience (#361)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->
Refactor booking system: update API endpoint format, add ResetEvents
event, and enhance UI components for improved user experience

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-21 16:25:34 +03:00
5589e5b587 Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience 2025-07-21 15:22:17 +03:00
d3bd363b70 Fix category check in schedule dialog for water heater 2025-07-21 15:10:15 +03:00
076c80fe44 Enhance garage door scheduling functionality and UI improvements 2025-07-17 17:02:23 +03:00
28 changed files with 335 additions and 464 deletions

View File

@ -10,7 +10,7 @@ part 'events_event.dart';
part 'events_state.dart'; part 'events_state.dart';
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> { class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
final EventController eventController = EventController(); EventController eventController = EventController();
final CalendarSystemService calendarService; final CalendarSystemService calendarService;
CalendarEventsBloc({ CalendarEventsBloc({
@ -20,7 +20,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
on<AddEvent>(_onAddEvent); on<AddEvent>(_onAddEvent);
on<DisposeResources>(_onDisposeResources); on<DisposeResources>(_onDisposeResources);
on<GoToWeek>(_onGoToWeek); on<GoToWeek>(_onGoToWeek);
on<ResetEvents>(_onResetEvents);
} }
Future<void> _onLoadEvents( Future<void> _onLoadEvents(
LoadEvents event, LoadEvents event,
Emitter<CalendarEventState> emit, Emitter<CalendarEventState> emit,
@ -126,4 +128,18 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
eventController.dispose(); eventController.dispose();
return super.close(); return super.close();
} }
void _onResetEvents(
ResetEvents event,
Emitter<CalendarEventState> emit,
) {
if (calendarService is MemoryCalendarServiceWithRemoteFallback) {
(calendarService as MemoryCalendarServiceWithRemoteFallback)
.memoryService
.clear();
}
eventController.dispose();
eventController = EventController();
emit(EventsInitial());
}
} }

View File

@ -29,3 +29,10 @@ class CheckWeekHasEvents extends CalendarEventsEvent {
final DateTime weekStart; final DateTime weekStart;
const CheckWeekHasEvents(this.weekStart); const CheckWeekHasEvents(this.weekStart);
} }
class ResetEvents extends CalendarEventsEvent {
const ResetEvents();
@override
List<Object?> get props => [];
}

View File

@ -26,7 +26,33 @@ class BookingPage extends StatefulWidget {
} }
class _BookingPageState extends State<BookingPage> { class _BookingPageState extends State<BookingPage> {
late final EventController _eventController; @override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
BlocProvider(create: (_) => DateSelectionBloc()),
BlocProvider(
create: (_) => CalendarEventsBloc(
calendarService: MemoryCalendarServiceWithRemoteFallback(
remoteService: RemoteCalendarService(HTTPService()),
memoryService: MemoryCalendarService(),
),
),
),
],
child: _BookingPageContent(),
);
}
}
class _BookingPageContent extends StatefulWidget {
@override
State<_BookingPageContent> createState() => _BookingPageContentState();
}
class _BookingPageContentState extends State<_BookingPageContent> {
late EventController _eventController;
@override @override
void initState() { void initState() {
@ -40,7 +66,7 @@ class _BookingPageState extends State<BookingPage> {
super.dispose(); super.dispose();
} }
void _dispatchLoadEvents(BuildContext context) { void _loadEvents(BuildContext context) {
final selectedRoom = final selectedRoom =
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace; context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
final dateState = context.read<DateSelectionBloc>().state; final dateState = context.read<DateSelectionBloc>().state;
@ -60,35 +86,24 @@ class _BookingPageState extends State<BookingPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
providers: [ listener: (context, state) {
BlocProvider(create: (_) => SelectedBookableSpaceBloc()), if (state.selectedBookableSpace != null) {
BlocProvider(create: (_) => DateSelectionBloc()), context.read<CalendarEventsBloc>().add(const ResetEvents());
BlocProvider( _loadEvents(context);
create: (_) => CalendarEventsBloc( }
calendarService: MemoryCalendarServiceWithRemoteFallback( },
remoteService: RemoteCalendarService( child: BlocListener<DateSelectionBloc, DateSelectionState>(
HTTPService(), listener: (context, state) {
), _loadEvents(context);
memoryService: MemoryCalendarService(), },
), child: BlocListener<CalendarEventsBloc, CalendarEventState>(
)),
],
child: Builder(
builder: (context) =>
BlocListener<CalendarEventsBloc, CalendarEventState>(
listenWhen: (prev, curr) => curr is EventsLoaded,
listener: (context, state) { listener: (context, state) {
if (state is EventsLoaded) { if (state is EventsLoaded) {
_eventController.removeWhere((_) => true); _eventController.removeWhere((_) => true);
_eventController.addAll(state.events); _eventController.addAll(state.events);
} }
}, },
child: BlocListener<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
listener: (context, state) => _dispatchLoadEvents(context),
child: BlocListener<DateSelectionBloc, DateSelectionState>(
listener: (context, state) => _dispatchLoadEvents(context),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -123,8 +138,8 @@ class _BookingPageState extends State<BookingPage> {
), ),
), ),
Expanded( Expanded(
child: BlocBuilder<DateSelectionBloc, child:
DateSelectionState>( BlocBuilder<DateSelectionBloc, DateSelectionState>(
builder: (context, dateState) { builder: (context, dateState) {
return CustomCalendarPage( return CustomCalendarPage(
selectedDate: dateState.selectedDate, selectedDate: dateState.selectedDate,
@ -169,8 +184,7 @@ class _BookingPageState extends State<BookingPage> {
), ),
], ],
), ),
BlocBuilder<DateSelectionBloc, BlocBuilder<DateSelectionBloc, DateSelectionState>(
DateSelectionState>(
builder: (context, state) { builder: (context, state) {
final weekStart = state.weekStart; final weekStart = state.weekStart;
final weekEnd = final weekEnd =
@ -193,6 +207,7 @@ class _BookingPageState extends State<BookingPage> {
), ),
], ],
), ),
const SizedBox(height: 20),
Expanded( Expanded(
flex: 5, flex: 5,
child: BlocBuilder<SelectedBookableSpaceBloc, child: BlocBuilder<SelectedBookableSpaceBloc,
@ -203,22 +218,16 @@ class _BookingPageState extends State<BookingPage> {
return BlocBuilder<DateSelectionBloc, return BlocBuilder<DateSelectionBloc,
DateSelectionState>( DateSelectionState>(
builder: (context, dateState) { builder: (context, dateState) {
return BlocListener<CalendarEventsBloc, return BlocBuilder<CalendarEventsBloc,
CalendarEventState>( CalendarEventState>(
listenWhen: (prev, curr) => builder: (context, eventState) {
curr is EventsLoaded, return WeeklyCalendarPage(
listener: (context, state) { key: ValueKey(
if (state is EventsLoaded) { selectedRoom?.uuid ?? 'no-room'),
_eventController
.removeWhere((_) => true);
_eventController.addAll(state.events);
}
},
child: WeeklyCalendarPage(
startTime: selectedRoom startTime: selectedRoom
?.bookableConfig.startTime, ?.bookableConfig.startTime,
endTime: selectedRoom endTime:
?.bookableConfig.endTime, selectedRoom?.bookableConfig.endTime,
weekStart: dateState.weekStart, weekStart: dateState.weekStart,
selectedDate: dateState.selectedDate, selectedDate: dateState.selectedDate,
eventController: _eventController, eventController: _eventController,
@ -226,7 +235,8 @@ class _BookingPageState extends State<BookingPage> {
.watch<DateSelectionBloc>() .watch<DateSelectionBloc>()
.state .state
.selectedDateFromSideBarCalender, .selectedDateFromSideBarCalender,
), );
},
); );
}, },
); );
@ -241,8 +251,6 @@ class _BookingPageState extends State<BookingPage> {
), ),
), ),
), ),
),
),
); );
} }
} }

View File

@ -76,21 +76,29 @@ class __SidebarContentState extends State<_SidebarContent> {
builder: (context, state) { builder: (context, state) {
return Column( return Column(
children: [ children: [
const _SidebarHeader(title: 'Spaces'), Padding(
Container( padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8.0),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1), color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, -2), offset: const Offset(0, 4),
blurRadius: 4, blurRadius: 4,
spreadRadius: 0, spreadRadius: 0,
), ),
],
),
child: _SidebarHeader(title: 'Spaces')),
),
Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1), color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, 2), offset: const Offset(0, 4),
blurRadius: 4, blurRadius: 4,
spreadRadius: 0, spreadRadius: 0,
), ),
@ -220,7 +228,7 @@ class _SidebarHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(10.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View File

@ -66,7 +66,16 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
); );
return CalendarDatePicker2( return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
),
),
child: CalendarDatePicker2(
config: config, config: config,
value: [_selectedDate], value: [_selectedDate],
onValueChanged: (dates) { onValueChanged: (dates) {
@ -78,6 +87,7 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
widget.onDateChanged(picked.day, picked.month, picked.year); widget.onDateChanged(picked.day, picked.month, picked.year);
} }
}, },
),
); );
} }
} }

View File

@ -32,15 +32,17 @@ class EventTileWidget extends StatelessWidget {
return Expanded( return Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isEventEnded color: isEventEnded
? ColorsManager.grayColor.withOpacity(0.1) ? ColorsManager.grayColor.withOpacity(0.1)
: ColorsManager.blue1.withOpacity(0.1), : ColorsManager.blue1.withOpacity(0.1),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: const Border( border: Border(
left: BorderSide( left: BorderSide(
color: ColorsManager.grayColor, color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.secondaryColor,
width: 4, width: 4,
), ),
), ),

View File

@ -21,7 +21,7 @@ class RoomListItem extends StatelessWidget {
groupValue: isSelected ? room.uuid : null, groupValue: isSelected ? room.uuid : null,
visualDensity: const VisualDensity(vertical: -4), visualDensity: const VisualDensity(vertical: -4),
onChanged: (value) => onTap(), onChanged: (value) => onTap(),
activeColor: ColorsManager.primaryColor, activeColor: ColorsManager.secondaryColor,
title: Text( title: Text(
room.spaceName, room.spaceName,
maxLines: 2, maxLines: 2,

View File

@ -14,26 +14,33 @@ class WeekDayHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return ColoredBox(
color: isSelectedDay
? ColorsManager.secondaryColor.withOpacity(0.1)
: Colors.transparent,
child: Column(
children: [ children: [
const SizedBox(
height: 10,
),
Text( Text(
DateFormat('EEE').format(date).toUpperCase(), DateFormat('EEE').format(date).toUpperCase(),
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 14, fontSize: 14,
color: isSelectedDay ? Colors.blue : Colors.black, color: ColorsManager.blackColor,
), ),
), ),
Text( Text(
DateFormat('d').format(date), DateFormat('d').format(date),
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 20, fontSize: 30,
color: color: ColorsManager.blackColor,
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
), ),
), ),
], ],
),
); );
} }
} }

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,13 +43,17 @@ class WeekNavigation extends StatelessWidget {
onPressed: onPreviousWeek, onPressed: onPreviousWeek,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( SizedBox(
width: 120,
child: Text(
_getMonthYearText(weekStart, weekEnd), _getMonthYearText(weekStart, weekEnd),
style: const TextStyle( style: const TextStyle(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
textAlign: TextAlign.center,
),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
IconButton( IconButton(

View File

@ -57,13 +57,10 @@ class WeeklyCalendarPage extends StatelessWidget {
); );
} }
const double timeLineWidth = 90;
const double timeLineWidth = 65;
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
bool isInRange(DateTime date, DateTime start, DateTime end) { bool isInRange(DateTime date, DateTime start, DateTime end) {
!date.isBefore(start) && !date.isAfter(end); !date.isBefore(start) && !date.isAfter(end);
// remove this line and Check if the date is within the range // remove this line and Check if the date is within the range
@ -100,7 +97,6 @@ class WeeklyCalendarPage extends StatelessWidget {
width: width, width: width,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.13), color: Colors.orange.withOpacity(0.13),
borderRadius: BorderRadius.circular(8),
), ),
); );
} else if (isSelected) { } else if (isSelected) {
@ -110,7 +106,6 @@ class WeeklyCalendarPage extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: color:
ColorsManager.spaceColor.withOpacity(0.07), ColorsManager.spaceColor.withOpacity(0.07),
borderRadius: BorderRadius.circular(8),
), ),
); );
} }
@ -143,7 +138,7 @@ class WeeklyCalendarPage extends StatelessWidget {
heightPerMinute: 1.7, heightPerMinute: 1.7,
showLiveTimeLineInAllDays: false, showLiveTimeLineInAllDays: false,
showVerticalLines: true, showVerticalLines: true,
emulateVerticalOffsetBy: -80, emulateVerticalOffsetBy: -95,
startDay: WeekDays.monday, startDay: WeekDays.monday,
liveTimeIndicatorSettings: liveTimeIndicatorSettings:
const LiveTimeIndicatorSettings( const LiveTimeIndicatorSettings(
@ -161,7 +156,7 @@ class WeeklyCalendarPage extends StatelessWidget {
}, },
timeLineWidth: timeLineWidth, timeLineWidth: timeLineWidth,
weekPageHeaderBuilder: (start, end) => Container(), weekPageHeaderBuilder: (start, end) => Container(),
weekTitleHeight: 60, weekTitleHeight: 90,
weekNumberBuilder: (firstDayOfWeek) => Padding( weekNumberBuilder: (firstDayOfWeek) => Padding(
padding: const EdgeInsets.only(right: 15, bottom: 10), padding: const EdgeInsets.only(right: 15, bottom: 10),
child: Column( child: Column(
@ -208,8 +203,6 @@ class WeeklyCalendarPage extends StatelessWidget {
}, },
); );
} }
} }
bool isSameDay(DateTime d1, DateTime d2) { bool isSameDay(DateTime d1, DateTime d2) {

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart';
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
@ -94,11 +95,18 @@ class GarageDoorControlView extends StatelessWidget
FetchGarageDoorSchedulesEvent( FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'doorcontact_state'), deviceId: deviceId, category: 'doorcontact_state'),
); );
showDialog(
showDialog<void>(
context: context, context: context,
builder: (ctx) => BlocProvider.value( builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<GarageDoorBloc>(context), value: BlocProvider.of<GarageDoorBloc>(context),
child: BuildGarageDoorScheduleView(status: status), child: BuildScheduleView(
deviceUuid: deviceId,
category: 'Timer',
code: 'doorcontact_state',
countdownCode: 'Timer',
deviceType: 'GD',
),
)); ));
}, },
name: 'Scheduling', name: 'Scheduling',

View File

@ -287,7 +287,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try { try {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
Status status = Status(code: '', value: ''); Status status = Status(code: '', value: '');
if (event.deviceType == 'CUR_2') { if (event.deviceType == 'CUR_2' ||
event.deviceType == 'GD' ) {
status = status.copyWith( status = status.copyWith(
code: 'control', code: 'control',
value: event.functionOn == true ? 'open' : 'close'); value: event.functionOn == true ? 'open' : 'close');

View File

@ -69,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
countDownCode: countDownCode), countDownCode: countDownCode),
); );
}, },
backgroundColor: ColorsManager.primaryColorWithOpacity, backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -63,7 +63,7 @@ class InchingModeButtons extends StatelessWidget {
), ),
); );
}, },
backgroundColor: ColorsManager.primaryColor, backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -31,11 +31,12 @@ class BuildScheduleView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => ScheduleBloc(deviceId: deviceUuid,) create: (_) => ScheduleBloc(
deviceId: deviceUuid,
)
..add(ScheduleGetEvent(category: category)) ..add(ScheduleGetEvent(category: category))
..add(ScheduleFetchStatusEvent( ..add(ScheduleFetchStatusEvent(
deviceId: deviceUuid, deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
countdownCode: countdownCode ?? '')),
child: Dialog( child: Dialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20), insetPadding: const EdgeInsets.all(20),
@ -56,7 +57,7 @@ class BuildScheduleView extends StatelessWidget {
children: [ children: [
const ScheduleHeader(), const ScheduleHeader(),
const SizedBox(height: 20), const SizedBox(height: 20),
if (deviceType == 'CUR_2') if (deviceType == 'CUR_2' || deviceType == 'GD')
const SizedBox() const SizedBox()
else else
ScheduleModeSelector( ScheduleModeSelector(
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
category: category, category: category,
time: '', time: '',
function: Status( function: Status(
code: code.toString(), code: code.toString(), value: true),
value: true),
days: [], days: [],
), ),
isEdit: false, isEdit: false,
@ -96,7 +96,7 @@ class BuildScheduleView extends StatelessWidget {
} }
}, },
), ),
if (deviceType != 'CUR_2') if (deviceType != 'CUR_2'|| deviceType != 'GD')
if (state.scheduleMode == ScheduleModes.countdown || if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching) state.scheduleMode == ScheduleModes.inching)
CountdownInchingView( CountdownInchingView(

View File

@ -24,12 +24,13 @@ class ScheduleManagementUI extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
width: 170, width: 177,
height: 40, height: 40,
child: DefaultButton( child: DefaultButton(
borderColor: ColorsManager.grayColor.withOpacity(0.5), borderWidth: 4,
padding: 2, borderColor: ColorsManager.neutralGray,
backgroundColor: ColorsManager.graysColor, padding: 8,
backgroundColor: ColorsManager.textFieldGreyColor,
borderRadius: 15, borderRadius: 15,
onPressed: onAddSchedule, onPressed: onAddSchedule,
child: Row( child: Row(

View File

@ -39,7 +39,7 @@ class ScheduleModeButtons extends StatelessWidget {
borderRadius: 8, borderRadius: 8,
height: 40, height: 40,
onPressed: onSave, onPressed: onSave,
backgroundColor: ColorsManager.primaryColorWithOpacity, backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -194,7 +194,7 @@ class _ScheduleTableView extends StatelessWidget {
child: Text(_getSelectedDays( child: Text(_getSelectedDays(
ScheduleModel.parseSelectedDays(schedule.days)))), ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))), Center(child: Text(formatIsoStringToTime(schedule.time, context))),
if (deviceType == 'CUR_2') if (deviceType == 'CUR_2' || deviceType == 'GD')
Center( Center(
child: Text(schedule.function.value == true ? 'open' : 'close')) child: Text(schedule.function.value == true ? 'open' : 'close'))
else else

View File

@ -23,7 +23,7 @@ class ScheduleDialogHelper {
required String deviceType, required String deviceType,
}) { }) {
bool temp; bool temp;
if (deviceType == 'CUR_2') { if (deviceType == 'CUR_2' || deviceType == 'GD') {
temp = schedule!.function.value == 'open' ? true : false; temp = schedule!.function.value == 'open' ? true : false;
} else { } else {
temp = schedule!.function.value; temp = schedule!.function.value;
@ -116,7 +116,7 @@ class ScheduleDialogHelper {
ScheduleModeButtons( ScheduleModeButtons(
onSave: () { onSave: () {
dynamic temp; dynamic temp;
if (deviceType == 'CUR_2') { if (deviceType == 'CUR_2' || deviceType == 'GD') {
temp = functionOn! ? 'open' : 'close'; temp = functionOn! ? 'open' : 'close';
} else { } else {
temp = functionOn; temp = functionOn;
@ -202,18 +202,23 @@ class ScheduleDialogHelper {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Radio<bool>( Radio<bool>(
activeColor: ColorsManager.secondaryColor,
focusColor: ColorsManager.secondaryColor,
value: true, value: true,
groupValue: isOn, groupValue: isOn,
onChanged: (val) => onChanged(true), onChanged: (val) => onChanged(true),
), ),
Text(categor == 'CUR_2' ? 'open' : 'On'), Text(categor == 'CUR_2' || categor == 'GD' ? 'open' : 'On'),
const SizedBox(width: 10), const SizedBox(width: 10),
Radio<bool>( Radio<bool>(
activeColor: ColorsManager.secondaryColor,
focusColor: ColorsManager.secondaryColor,
value: false, value: false,
groupValue: isOn, groupValue: isOn,
onChanged: (val) => onChanged(false), onChanged: (val) => onChanged(false),
), ),
Text(categor == 'CUR_2' ? 'close' : 'Off'), Text(categor == 'CUR_2' || categor == 'GD' ? 'close' : 'Off'),
], ],
); );
} }

View File

@ -9,8 +9,6 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
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/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
@ -27,16 +25,15 @@ class SpaceManagementPage extends StatefulWidget {
class _SpaceManagementPageState extends State<SpaceManagementPage> { class _SpaceManagementPageState extends State<SpaceManagementPage> {
late final CommunitiesBloc communitiesBloc; late final CommunitiesBloc communitiesBloc;
late final HTTPService _httpService;
@override @override
void initState() { void initState() {
_httpService = HTTPService();
communitiesBloc = CommunitiesBloc( communitiesBloc = CommunitiesBloc(
communitiesService: DebouncedCommunitiesService( communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(_httpService), RemoteCommunitiesService(HTTPService()),
), ),
)..add(const LoadCommunities(LoadCommunitiesParam())); )..add(const LoadCommunities(LoadCommunitiesParam()));
super.initState(); super.initState();
} }
@ -53,18 +50,13 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
BlocProvider( BlocProvider(
create: (context) => SpaceDetailsBloc( create: (context) => SpaceDetailsBloc(
UniqueSpaceDetailsSpacesDecoratorService( UniqueSpaceDetailsSpacesDecoratorService(
RemoteSpaceDetailsService(httpService: _httpService), RemoteSpaceDetailsService(httpService: HTTPService()),
), ),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => ProductsBloc( create: (context) => ProductsBloc(
RemoteProductsService(_httpService), RemoteProductsService(HTTPService()),
),
),
BlocProvider(
create: (context) => ReorderSpacesBloc(
RemoteReorderSpacesService(_httpService),
), ),
), ),
], ],

View File

@ -11,8 +11,6 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.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/communities/presentation/bloc/communities_bloc.dart'; 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/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -166,16 +164,6 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
context.read<CommunitiesBloc>().add( context.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(newCommunity), CommunitiesUpdateCommunity(newCommunity),
); );
context.read<ReorderSpacesBloc>().add(
ReorderSpacesEvent(
ReorderSpacesParam(
communityUuid: widget.community.uuid,
parentSpaceUuid: data.parent?.uuid ?? '',
spaces: children,
),
),
);
} }
void _onSpaceTapped(SpaceModel? space) { void _onSpaceTapped(SpaceModel? space) {
@ -257,13 +245,6 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final levelXOffset = <int, double>{}; final levelXOffset = <int, double>{};
_calculateLayout(community.spaces, 0, levelXOffset); _calculateLayout(community.spaces, 0, levelXOffset);
const horizontalCanvasPadding = 100.0;
final originalPositions = Map.of(_positions);
_positions.clear();
for (final entry in originalPositions.entries) {
_positions[entry.key] = entry.value.translate(horizontalCanvasPadding, 0);
}
final selectedSpace = widget.selectedSpace; final selectedSpace = widget.selectedSpace;
final highlightedUuids = <String>{}; final highlightedUuids = <String>{};
if (selectedSpace != null) { if (selectedSpace != null) {
@ -281,7 +262,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
community: widget.community, community: widget.community,
); );
final createButtonX = (levelXOffset[0] ?? 0.0) + horizontalCanvasPadding; final createButtonX = levelXOffset[0] ?? 0.0;
const createButtonY = 0.0; const createButtonY = 0.0;
widgets.add( widgets.add(
@ -313,12 +294,10 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
CommunityModel? community, CommunityModel? community,
SpaceModel? parent, SpaceModel? parent,
}) { }) {
const targetWidth = 40.0;
final padding = (_horizontalSpacing - targetWidth) / 2;
if (spaces.isNotEmpty) { if (spaces.isNotEmpty) {
final firstChildPos = _positions[spaces.first.uuid]!; final firstChildPos = _positions[spaces.first.uuid]!;
final targetPos = Offset( final targetPos = Offset(
firstChildPos.dx - padding - targetWidth, firstChildPos.dx - (_horizontalSpacing / 4),
firstChildPos.dy, firstChildPos.dy,
); );
widgets.add(_buildDropTarget(parent, community, 0, targetPos)); widgets.add(_buildDropTarget(parent, community, 0, targetPos));
@ -400,7 +379,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
); );
final targetPos = Offset( final targetPos = Offset(
position.dx + cardWidth + padding, position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
position.dy, position.dy,
); );
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos)); widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
@ -435,33 +414,24 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: DragTarget<SpaceReorderDataModel>( child: DragTarget<SpaceReorderDataModel>(
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
if (_draggedData == null) { if (_draggedData == null) {
return const SizedBox.shrink(); return const SizedBox();
} }
final children = parent?.children ?? community?.spaces ?? []; final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid &&
final isSameParent = (_draggedData!.parent?.uuid == parent?.uuid && _draggedData?.community == null) ||
_draggedData!.community == null) || (_draggedData?.community?.uuid == community?.uuid &&
(_draggedData!.community?.uuid == community?.uuid && _draggedData?.parent == null);
_draggedData!.parent == null);
if (!isSameParent) { if (!isTargetForDragged) {
return const SizedBox.shrink(); return const SizedBox();
} }
final oldIndex = return Container(
children.indexWhere((s) => s.uuid == _draggedData!.space.uuid);
if (oldIndex != -1 && (oldIndex == index || oldIndex == index - 1)) {
return const SizedBox.shrink();
}
return AnimatedContainer(
duration: const Duration(milliseconds: 150),
width: 40, width: 40,
alignment: Alignment.center,
height: _cardHeight, height: _cardHeight,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.theme.colorScheme.primary.withValues( color: context.theme.colorScheme.primary.withValues(
alpha: candidateData.isNotEmpty ? 0.9 : 0.3, alpha: candidateData.isNotEmpty ? 0.7 : 0.3,
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@ -484,9 +454,6 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final oldIndex = final oldIndex =
children.indexWhere((s) => s.uuid == data.data.space.uuid); children.indexWhere((s) => s.uuid == data.data.space.uuid);
if (oldIndex == -1) {
return true;
}
if (oldIndex == index || oldIndex == index - 1) { if (oldIndex == index || oldIndex == index - 1) {
return false; return false;
} }
@ -514,7 +481,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: SizedBox( child: SizedBox(
width: context.screenWidth * 5, width: context.screenWidth * 5,
height: context.screenHeight * 5, height: context.screenHeight * 5,
child: Stack(clipBehavior: Clip.none, children: treeWidgets), child: Stack(children: treeWidgets),
), ),
), ),
); );

View File

@ -1,58 +0,0 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_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 RemoteReorderSpacesService implements ReorderSpacesService {
RemoteReorderSpacesService(this._httpClient);
final HTTPService _httpClient;
@override
Future<void> reorderSpaces(ReorderSpacesParam param) async {
try {
await _httpClient.post(
path: await _makeUrl(param),
body: param.toJson(),
expectedResponseModel: (json) => json,
);
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
throw APIException(_getErrorMessageFromBody(message));
} catch (e) {
throw APIException(e.toString());
}
}
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
if (body == null) return 'Failed to delete space';
final error = body['error'] as Map<String, dynamic>?;
final errorMessage = error?['message'] as String? ?? '';
return errorMessage;
}
Future<String> _makeUrl(ReorderSpacesParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
final communityUuid = param.communityUuid;
if (projectUuid == null || projectUuid.isEmpty) {
throw APIException('Project UUID is not set');
}
if (communityUuid.isEmpty) {
throw APIException('Community UUID is not set');
}
if (param.parentSpaceUuid.isEmpty) {
throw APIException('Parent Space UUID is not set');
}
return ApiEndpoints.reorderSpaces
.replaceAll('{projectUuid}', projectUuid)
.replaceAll('{communityUuid}', communityUuid)
.replaceAll('{parentSpaceUuid}', param.parentSpaceUuid);
}
}

View File

@ -1,21 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
class ReorderSpacesParam extends Equatable {
const ReorderSpacesParam({
required this.communityUuid,
required this.parentSpaceUuid,
required this.spaces,
});
final String communityUuid;
final String parentSpaceUuid;
final List<SpaceModel> spaces;
@override
List<Object?> get props => [spaces, communityUuid, parentSpaceUuid];
Map<String, dynamic> toJson() => {
'spacesUuids': spaces.map((space) => space.uuid).toList(),
};
}

View File

@ -1,5 +0,0 @@
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
abstract interface class ReorderSpacesService {
Future<void> reorderSpaces(ReorderSpacesParam param);
}

View File

@ -1,35 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'reorder_spaces_event.dart';
part 'reorder_spaces_state.dart';
class ReorderSpacesBloc extends Bloc<ReorderSpacesEvent, ReorderSpacesState> {
ReorderSpacesBloc(
this._reorderSpacesService,
) : super(const ReorderSpacesInitial()) {
on<ReorderSpacesEvent>(_onReorderSpacesEvent);
}
final ReorderSpacesService _reorderSpacesService;
Future<void> _onReorderSpacesEvent(
ReorderSpacesEvent event,
Emitter<ReorderSpacesState> emit,
) async {
emit(const ReorderSpacesLoading());
try {
await _reorderSpacesService.reorderSpaces(event.param);
emit(const ReorderSpacesSuccess());
} on APIException catch (e) {
emit(ReorderSpacesFailure(e.message));
} catch (e) {
emit(ReorderSpacesFailure(e.toString()));
} finally {
emit(const ReorderSpacesInitial());
}
}
}

View File

@ -1,10 +0,0 @@
part of 'reorder_spaces_bloc.dart';
final class ReorderSpacesEvent extends Equatable {
const ReorderSpacesEvent(this.param);
final ReorderSpacesParam param;
@override
List<Object> get props => [param];
}

View File

@ -1,29 +0,0 @@
part of 'reorder_spaces_bloc.dart';
sealed class ReorderSpacesState extends Equatable {
const ReorderSpacesState();
@override
List<Object> get props => [];
}
final class ReorderSpacesInitial extends ReorderSpacesState {
const ReorderSpacesInitial();
}
final class ReorderSpacesLoading extends ReorderSpacesState {
const ReorderSpacesLoading();
}
final class ReorderSpacesSuccess extends ReorderSpacesState {
const ReorderSpacesSuccess();
}
final class ReorderSpacesFailure extends ReorderSpacesState {
const ReorderSpacesFailure(this.errorMessage);
final String errorMessage;
@override
List<Object> get props => [errorMessage];
}

View File

@ -41,8 +41,6 @@ abstract class ApiEndpoints {
'/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = static const String getSpaceHierarchy =
'/projects/{projectId}/communities/{communityId}/spaces'; '/projects/{projectId}/communities/{communityId}/spaces';
static const String reorderSpaces =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{parentSpaceUuid}/spaces/order';
// Community Module // Community Module
static const String createCommunity = '/projects/{projectId}/communities'; static const String createCommunity = '/projects/{projectId}/communities';
@ -142,6 +140,5 @@ abstract class ApiEndpoints {
static const String saveSchedule = '/schedule/{deviceUuid}'; static const String saveSchedule = '/schedule/{deviceUuid}';
static const String getBookableSpaces = '/bookable-spaces'; static const String getBookableSpaces = '/bookable-spaces';
static const String getBookings = static const String getBookings = '/bookings?month={mm}-{yyyy}&space={space}';
'/bookings?month={mm}%2F{yyyy}&space={space}';
} }