Compare commits

...

24 Commits

Author SHA1 Message Date
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
d12b4c0c65 Sp 1721 fe implement delete space feature (#351)
<!--
  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:
-->

## Jira Ticket
[SP-1721](https://syncrow.atlassian.net/browse/SP-1721)

## Description

Implemented delete space feature.
Smoothened out the connective lines in the canvas.
Synced state between selection and communities bloc on delete.
Implemented create space feature.

## Type of Change

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

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ 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 


[SP-1721]:
https://syncrow.atlassian.net/browse/SP-1721?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-17 16:49:20 +03:00
22c8c54fab Enhance booking system: update API endpoints, improve event loading w… (#357)
…ith caching, and refine UI components

<!--
  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 booking system: update API endpoints, improve event loading with
caching, and refine UI components

## Type of Change

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

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ 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-17 16:23:03 +03:00
95300071e9 [FE] When user navigates to devices page the devices are already listed although no community is selected also when we select a community the API is being called repeatedly too many times (#356)
<!--
  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:
-->

## Jira Ticket
[SP-1589](https://syncrow.atlassian.net/browse/SP-1589)

## Description
fixed the issue of community selection (if empty no devices should
appear)
## 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 


[SP-1589]:
https://syncrow.atlassian.net/browse/SP-1589?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-17 15:07:46 +03:00
7876af9756 Refactor memory service implementation: rename MemoryBookableSpaceService to MemoryCalendarService for clarity and consistency 2025-07-17 12:46:07 +03:00
fe2f4a872b Refactor memory event handling: replace MemoryBookableSpaceService with a new implementation and integrate caching logic in CalendarEventsBloc 2025-07-17 12:38:58 +03:00
c9b8fbb0c2 Refactor calendar event loading: replace parameters with LoadEventsParam class and implement memory caching for improved performance 2025-07-17 11:20:32 +03:00
c6729f476f Enhance booking system: update API endpoints, improve event loading with caching, and refine UI components 2025-07-16 15:36:49 +03:00
3e634dc7a2 fix communities filtiring issue 2025-07-16 11:02:32 +03:00
75b9f4a4e6 changed the title from Routine to Workflow Automation (#354)
<!--
  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:
-->

## Jira Ticket
[SP-1851](https://syncrow.atlassian.net/browse/SP-1851)

## Description

change the Title to Work Flow 

## 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 


[SP-1851]:
https://syncrow.atlassian.net/browse/SP-1851?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-15 11:54:29 +03:00
fe4063ef8f changed the title from Routine to Workflow Automation 2025-07-15 11:20:13 +03:00
029b5d32e0 Adjust table scroll behavior and modify height for improved layout (#353)
<!--
  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 -->
Adjust table scroll behavior and modify height for improved layout

## 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-15 10:43:22 +03:00
428c81efff Adjust table scroll behavior and modify height for improved layout 2025-07-15 10:05:07 +03:00
288c252f46 SP-1696-fe-edit-user-dialog-enhancements (#347)
<!--
  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:
-->

## Jira Ticket
[SP-1696](https://syncrow.atlassian.net/browse/SP-1696)

## Description

add company Name and replace it with job title

## Type of Change

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

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ 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 


[SP-1696]:
https://syncrow.atlassian.net/browse/SP-1696?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-15 09:08:15 +03:00
7399dee687 [FE] Redundant API calls on Routines page when selecting a community from the tree (#345)
…rom the tree

<!--
  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:
-->

## Jira Ticket
[SP-1800](https://syncrow.atlassian.net/browse/SP-1800)

## Description

fix Redundant API calls when choosing devices and use Que Par to send
spaces instead of send api for every space

## 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 


[SP-1800]:
https://syncrow.atlassian.net/browse/SP-1800?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-15 08:32:08 +03:00
08e2ed4b4c Merge branch 'dev' into revert-SP-1589 2025-07-15 08:31:45 +03:00
59e04708cd Merge branch 'main' into revert-SP-1589 2025-07-15 08:30:33 +03:00
338d4f5737 fix typo 2025-07-15 08:19:43 +03:00
5532935a3a Enhance UI components: update color management, adjust button styles,… (#350)
… and improve text formatting for better readability

<!--
  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 UI components: update color 
## 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-14 16:15:42 +03:00
249cbfc242 Merge branch 'dev' into Build-Schedule-List-View-Support-State-Persistence 2025-07-14 15:20:08 +03:00
8167926620 Enhance UI components: update color management, adjust button styles, and improve text formatting for better readability 2025-07-14 15:14:56 +03:00
2681c837f5 add company name and replace it with job title 2025-07-11 12:10:53 +03:00
b6664ec1ba fix Redundant API calls on Routines page when selecting a community from the tree 2025-07-11 10:53:04 +03:00
dcf1df9b4a sp1613 delete condition word 2025-05-21 07:25:34 -05:00
36 changed files with 804 additions and 676 deletions

View File

@ -0,0 +1,63 @@
import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
class MemoryCalendarService implements CalendarSystemService {
final Map<String, CalendarEventsResponse> _eventsCache = {};
@override
Future<CalendarEventsResponse> getCalendarEvents({
required LoadEventsParam params,
}) async {
final key = params.generateKey();
return _eventsCache[key]!;
}
void setEvents(
LoadEventsParam param,
CalendarEventsResponse events,
) {
final key = param.generateKey();
_eventsCache[key] = events;
}
void addEvent(LoadEventsParam param, CalendarEventsResponse event) {
final key = param.generateKey();
_eventsCache[key] = event;
}
void clear() {
_eventsCache.clear();
}
}
class MemoryCalendarServiceWithRemoteFallback implements CalendarSystemService {
final MemoryCalendarService memoryService;
final RemoteCalendarService remoteService;
MemoryCalendarServiceWithRemoteFallback({
required this.memoryService,
required this.remoteService,
});
@override
Future<CalendarEventsResponse> getCalendarEvents({
required LoadEventsParam params,
}) async {
final key = params.generateKey();
final doesExistInMemory = memoryService._eventsCache.containsKey(key);
if (doesExistInMemory) {
return memoryService.getCalendarEvents(params: params);
} else {
final remoteResult =
await remoteService.getCalendarEvents(params: params);
memoryService.setEvents(params, remoteResult);
return remoteResult;
}
}
}

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
@ -13,147 +14,21 @@ class RemoteCalendarService implements CalendarSystemService {
@override @override
Future<CalendarEventsResponse> getCalendarEvents({ Future<CalendarEventsResponse> getCalendarEvents({
required String spaceId, required LoadEventsParam params,
}) async { }) async {
try { final month = params.startDate.month.toString().padLeft(2, '0');
final response = await _httpService.get( final year = params.startDate.year.toString();
path: ApiEndpoints.getCalendarEvents,
queryParameters: {
'spaceId': spaceId,
},
expectedResponseModel: (json) {
return CalendarEventsResponse.fromJson(
json as Map<String, dynamic>,
);
},
);
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>); try {
} on DioException catch (e) { return await _httpService.get<CalendarEventsResponse>(
final responseData = e.response?.data; path: ApiEndpoints.getBookings
if (responseData is Map<String, dynamic>) { .replaceAll('{mm}', month)
final errorMessage = responseData['error']?['message'] as String? ?? .replaceAll('{yyyy}', year)
responseData['message'] as String? ?? .replaceAll('{space}', params.id),
_defaultErrorMessage; expectedResponseModel: (json) {
throw APIException(errorMessage); return CalendarEventsResponse.fromJson(json as Map<String, dynamic>);
} },
throw APIException(_defaultErrorMessage); );
} catch (e) {
throw APIException('$_defaultErrorMessage: ${e.toString()}');
}
}
}
class FakeRemoteCalendarService implements CalendarSystemService {
const FakeRemoteCalendarService(this._httpService, {this.useDummy = false});
final HTTPService _httpService;
final bool useDummy;
static const _defaultErrorMessage = 'Failed to load Calendar';
@override
Future<CalendarEventsResponse> getCalendarEvents({
required String spaceId,
}) async {
if (useDummy) {
final dummyJson = {
'statusCode': 200,
'message': 'Successfully fetched all bookings',
'data': [
{
'uuid': 'd4553fa6-a0c9-4f42-81c9-99a13a57bf80',
'date': '2025-07-11T10:22:00.626Z',
'startTime': '09:00:00',
'endTime': '12:00:00',
'cost': 10,
'user': {
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
'firstName': 'salsabeel',
'lastName': 'abuzaid',
'email': 'test@test.com',
'companyName': null
},
'space': {
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
'spaceName': '2(1)'
}
},
{
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
'date': '2025-07-11T10:22:00.626Z',
'startTime': '12:00:00',
'endTime': '13:00:00',
'cost': 10,
'user': {
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
'firstName': 'salsabeel',
'lastName': 'abuzaid',
'email': 'test@test.com',
'companyName': null
},
'space': {
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
'spaceName': '2(1)'
}
},
{
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
'date': '2025-07-13T10:22:00.626Z',
'startTime': '15:30:00',
'endTime': '19:00:00',
'cost': 20,
'user': {
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
'firstName': 'salsabeel',
'lastName': 'abuzaid',
'email': 'test@test.com',
'companyName': null
},
'space': {
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
'spaceName': '2(1)'
}
}
],
'success': true
};
final response = CalendarEventsResponse.fromJson(dummyJson);
// Filter events by spaceId
final filteredData = response.data.where((event) {
return event.space.uuid == spaceId;
}).toList();
print('Filtering events for spaceId: $spaceId');
print('Found ${filteredData.length} matching events');
return filteredData.isNotEmpty
? CalendarEventsResponse(
statusCode: response.statusCode,
message: response.message,
data: filteredData,
success: response.success,
)
: CalendarEventsResponse(
statusCode: 404,
message: 'No events found for spaceId: $spaceId',
data: [],
success: false,
);
}
try {
final response = await _httpService.get(
path: ApiEndpoints.getCalendarEvents,
queryParameters: {
'spaceId': spaceId,
},
expectedResponseModel: (json) {
return CalendarEventsResponse.fromJson(
json as Map<String, dynamic>,
);
},
);
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
} on DioException catch (e) { } on DioException catch (e) {
final responseData = e.response?.data; final responseData = e.response?.data;
if (responseData is Map<String, dynamic>) { if (responseData is Map<String, dynamic>) {

View File

@ -0,0 +1,34 @@
import 'package:equatable/equatable.dart';
class LoadEventsParam extends Equatable {
final DateTime startDate;
final DateTime endDate;
final String id;
const LoadEventsParam({
required this.startDate,
required this.endDate,
required this.id,
});
@override
List<Object?> get props => [startDate, endDate, id];
LoadEventsParam copyWith({
DateTime? startDate,
DateTime? endDate,
String? id,
}) {
return LoadEventsParam(
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
id: id ?? this.id,
);
}
}
extension KeyGenerator on LoadEventsParam {
String generateKey() {
return '$id-${startDate.year}-${startDate.month.toString().padLeft(2, '0')}';
}
}

View File

@ -1,7 +1,8 @@
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
abstract class CalendarSystemService { abstract class CalendarSystemService {
Future<CalendarEventsResponse> getCalendarEvents({ Future<CalendarEventsResponse> getCalendarEvents({
required String spaceId, required LoadEventsParam params,
}); });
} }

View File

@ -2,37 +2,48 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart'; import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
import 'package:syncrow_web/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart';
part 'events_event.dart'; 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({required this.calendarService}) : super(EventsInitial()) { CalendarEventsBloc({
required this.calendarService,
}) : super(EventsInitial()) {
on<LoadEvents>(_onLoadEvents); on<LoadEvents>(_onLoadEvents);
on<AddEvent>(_onAddEvent); on<AddEvent>(_onAddEvent);
on<StartTimer>(_onStartTimer);
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,
) async { ) async {
final param = event.param;
final month = param.endDate.month;
final year = param.endDate.year;
final spaceId = param.id;
emit(EventsLoading()); emit(EventsLoading());
try { try {
final response = await calendarService.getCalendarEvents( final response = await calendarService.getCalendarEvents(params: param);
spaceId: event.spaceId,
); final events = response.data.map(_toCalendarEventData).toList();
final events =
response.data.map<CalendarEventData>(_toCalendarEventData).toList();
eventController.addAll(events); eventController.addAll(events);
emit(EventsLoaded(events: events)); emit(EventsLoaded(
events: events,
spaceId: spaceId,
month: month,
year: year,
));
} catch (e) { } catch (e) {
emit(EventsError('Failed to load events')); emit(EventsError('Failed to load events'));
} }
@ -40,16 +51,19 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) { void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
eventController.add(event.event); eventController.add(event.event);
if (state is EventsLoaded) { if (state is EventsLoaded) {
final loaded = state as EventsLoaded; final loaded = state as EventsLoaded;
emit(EventsLoaded( emit(EventsLoaded(
events: [...eventController.events], events: [...eventController.events],
spaceId: loaded.spaceId,
month: loaded.month,
year: loaded.year,
)); ));
} }
} }
void _onStartTimer(StartTimer event, Emitter<CalendarEventState> emit) {}
void _onDisposeResources( void _onDisposeResources(
DisposeResources event, Emitter<CalendarEventState> emit) { DisposeResources event, Emitter<CalendarEventState> emit) {
eventController.dispose(); eventController.dispose();
@ -61,6 +75,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
final newWeekDays = _getWeekDays(event.weekDate); final newWeekDays = _getWeekDays(event.weekDate);
emit(EventsLoaded( emit(EventsLoaded(
events: loaded.events, events: loaded.events,
spaceId: loaded.spaceId,
month: loaded.month,
year: loaded.year,
)); ));
} }
} }
@ -93,8 +110,7 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
date: startTime, date: startTime,
startTime: startTime, startTime: startTime,
endTime: endTime, endTime: endTime,
title: title: '${booking.user.firstName} ${booking.user.lastName}',
'${booking.space.spaceName} - ${booking.user.firstName} ${booking.user.lastName}',
description: 'Cost: ${booking.cost}', description: 'Cost: ${booking.cost}',
color: Colors.blue, color: Colors.blue,
event: booking, event: booking,
@ -112,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

@ -6,17 +6,11 @@ abstract class CalendarEventsEvent {
} }
class LoadEvents extends CalendarEventsEvent { class LoadEvents extends CalendarEventsEvent {
final String spaceId; final LoadEventsParam param;
final DateTime weekStart; const LoadEvents(this.param);
final DateTime weekEnd;
const LoadEvents({
required this.spaceId,
required this.weekStart,
required this.weekEnd,
});
} }
class AddEvent extends CalendarEventsEvent { class AddEvent extends CalendarEventsEvent {
final CalendarEventData event; final CalendarEventData event;
const AddEvent(this.event); const AddEvent(this.event);
@ -35,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

@ -7,11 +7,17 @@ class EventsInitial extends CalendarEventState {}
class EventsLoading extends CalendarEventState {} class EventsLoading extends CalendarEventState {}
class EventsLoaded extends CalendarEventState { final class EventsLoaded extends CalendarEventState {
final List<CalendarEventData> events; final List<CalendarEventData> events;
final String spaceId;
final int month;
final int year;
EventsLoaded({ EventsLoaded({
required this.events, required this.events,
required this.spaceId,
required this.month,
required this.year,
}); });
} }

View File

@ -1,12 +1,15 @@
// booking_page.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart'; import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/selected_bookable_space_bloc/selected_bookable_space_bloc.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/selected_bookable_space_bloc/selected_bookable_space_bloc.dart';
import 'package:syncrow_web/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
@ -24,7 +27,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() {
@ -38,7 +67,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;
@ -46,9 +75,11 @@ class _BookingPageState extends State<BookingPage> {
if (selectedRoom != null) { if (selectedRoom != null) {
context.read<CalendarEventsBloc>().add( context.read<CalendarEventsBloc>().add(
LoadEvents( LoadEvents(
spaceId: selectedRoom.uuid, LoadEventsParam(
weekStart: dateState.weekStart, startDate: dateState.weekStart,
weekEnd: dateState.weekStart.add(const Duration(days: 6)), endDate: dateState.weekStart.add(const Duration(days: 6)),
id: selectedRoom.uuid,
),
), ),
); );
} }
@ -56,32 +87,25 @@ 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()), // Reset events and clear cache when room changes
BlocProvider( context.read<CalendarEventsBloc>().add(ResetEvents());
create: (_) => CalendarEventsBloc( _loadEvents(context);
calendarService: }
FakeRemoteCalendarService(HTTPService(), useDummy: true), },
), child: BlocListener<DateSelectionBloc, DateSelectionState>(
), listener: (context, state) {
], _loadEvents(context);
child: Builder( },
builder: (context) => child: BlocListener<CalendarEventsBloc, CalendarEventState>(
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: [
@ -116,8 +140,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,
@ -138,7 +162,7 @@ class _BookingPageState extends State<BookingPage> {
), ),
), ),
Expanded( Expanded(
flex: 4, flex: 5,
child: Padding( child: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
@ -162,8 +186,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 =
@ -186,7 +209,9 @@ class _BookingPageState extends State<BookingPage> {
), ),
], ],
), ),
const SizedBox(height: 20),
Expanded( Expanded(
flex: 5,
child: BlocBuilder<SelectedBookableSpaceBloc, child: BlocBuilder<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>( SelectedBookableSpaceState>(
builder: (context, roomState) { builder: (context, roomState) {
@ -195,22 +220,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,
@ -218,7 +237,9 @@ class _BookingPageState extends State<BookingPage> {
.watch<DateSelectionBloc>() .watch<DateSelectionBloc>()
.state .state
.selectedDateFromSideBarCalender, .selectedDateFromSideBarCalender,
), // isLoading: eventState is EventsLoading,
);
},
); );
}, },
); );
@ -233,8 +254,6 @@ class _BookingPageState extends State<BookingPage> {
), ),
), ),
), ),
),
),
); );
} }
} }

View File

@ -72,29 +72,33 @@ class __SidebarContentState extends State<_SidebarContent> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocConsumer<SidebarBloc, SidebarState>( return BlocConsumer<SidebarBloc, SidebarState>(
listener: (context, state) { listener: (context, state) {},
if (state.currentPage == 1 && searchController.text.isNotEmpty) {
searchController.clear();
}
},
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,
), ),
@ -147,6 +151,7 @@ class __SidebarContentState extends State<_SidebarContent> {
IconButton( IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () { onPressed: () {
searchController.clear();
context.read<SidebarBloc>().add(ResetSearch()); context.read<SidebarBloc>().add(ResetSearch());
}, },
), ),
@ -223,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

@ -1,16 +1,15 @@
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class EventTileWidget extends StatelessWidget { class EventTileWidget extends StatelessWidget {
final List<CalendarEventData<Object?>> events; final List<CalendarEventData<Object?>> events;
const EventTileWidget({ const EventTileWidget({
super.key, super.key,
required this.events, required this.events,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -18,38 +17,87 @@ class EventTileWidget extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: events.map((event) { children: events.map((event) {
final bool isEventEnded = final booking = event.event is CalendarEventBooking
? event.event! as CalendarEventBooking
: null;
final companyName = booking?.user.companyName ?? 'Unknown Company';
final startTime = DateFormat('hh:mm a').format(event.startTime!);
final endTime = DateFormat('hh:mm a').format(event.endTime!);
final isEventEnded =
event.endTime != null && event.endTime!.isBefore(DateTime.now()); event.endTime != null && event.endTime!.isBefore(DateTime.now());
final duration = event.endTime!.difference(event.startTime!);
final bool isLongEnough = duration.inMinutes >= 31;
return Expanded( return Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isEventEnded color: isEventEnded
? ColorsManager.lightGrayBorderColor ? ColorsManager.grayColor.withOpacity(0.1)
: ColorsManager.blue1.withOpacity(0.25), : ColorsManager.blue1.withOpacity(0.1),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border(
left: BorderSide(
color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.secondaryColor,
width: 4,
), ),
child: Column( ),
),
child: isLongEnough
? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
DateFormat('h:mm a').format(event.startTime!), '$startTime - $endTime',
style: const TextStyle( overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.bold, style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.black87, color: isEventEnded
? ColorsManager.grayColor.withOpacity(0.9)
: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
event.title, event.title,
style: const TextStyle( overflow: TextOverflow.ellipsis,
fontSize: 12, style: TextStyle(
color: ColorsManager.blackColor, fontSize: 14,
color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.blackColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
Text(
companyName,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: isEventEnded
? ColorsManager.grayColor.withOpacity(0.9)
: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
), ),
), ),
], ],
)
: Text(
event.title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.blackColor,
fontWeight: FontWeight.bold,
),
), ),
), ),
); );

View File

@ -21,20 +21,24 @@ 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,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
overflow: TextOverflow.ellipsis,
fontSize: 12), fontSize: 12),
), ),
subtitle: Text( subtitle: Text(
room.virtualLocation, room.virtualLocation,
maxLines: 2,
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: ColorsManager.textGray, color: ColorsManager.textGray,
overflow: TextOverflow.ellipsis,
), ),
), ),
); );

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

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart';
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart'; import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -23,6 +22,12 @@ class WeeklyCalendarPage extends StatelessWidget {
this.endTime, this.endTime,
this.selectedDateFromSideBarCalender, this.selectedDateFromSideBarCalender,
}); });
static const double timeLineWidth = 65;
static const int totalDays = 7;
static const double dayColumnWidth = 220;
final double calendarContentWidth =
timeLineWidth + (totalDays * dayColumnWidth);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -52,33 +57,28 @@ class WeeklyCalendarPage extends StatelessWidget {
); );
} }
final weekDays = _getWeekDays(weekStart); const double timeLineWidth = 90;
final selectedDayIndex =
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
final selectedSidebarIndex = selectedDateFromSideBarCalender == null
? -1
: weekDays
.indexWhere((d) => isSameDay(d, selectedDateFromSideBarCalender!));
const double timeLineWidth = 80;
const int totalDays = 7;
final DateTime highlightStart = DateTime(2025, 7, 10);
final DateTime highlightEnd = DateTime(2025, 7, 19);
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final double calendarWidth = constraints.maxWidth;
final double dayColumnWidth =
(calendarWidth - timeLineWidth) / totalDays - 0.1;
bool isInRange(DateTime date, DateTime start, DateTime end) { bool isInRange(DateTime date, DateTime start, DateTime end) {
return !date.isBefore(start) && !date.isAfter(end); !date.isBefore(start) && !date.isAfter(end);
// remove this line and Check if the date is within the range
return false;
} }
return Padding( return SingleChildScrollView(
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25), scrollDirection: Axis.horizontal,
child: SizedBox(
width: calendarContentWidth,
child: Padding(
padding:
const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
child: Stack( child: Stack(
children: [ children: [
WeekView( Container(
child: WeekView(
minuteSlotSize: MinuteSlotSize.minutes15,
weekDetectorBuilder: ({ weekDetectorBuilder: ({
required date, required date,
required height, required height,
@ -86,28 +86,62 @@ class WeeklyCalendarPage extends StatelessWidget {
required minuteSlotSize, required minuteSlotSize,
required width, required width,
}) { }) {
return isInRange(date, highlightStart, highlightEnd) final isSelected = isSameDay(date, selectedDate);
? HatchedColumnBackground( final isSidebarSelected =
backgroundColor: ColorsManager.grey800, selectedDateFromSideBarCalender != null &&
lineColor: ColorsManager.textGray, isSameDay(
opacity: 0.3, date, selectedDateFromSideBarCalender!);
stripeSpacing: 12, if (isSidebarSelected && !isSelected) {
borderRadius: BorderRadius.circular(8), return Container(
) height: height,
: const SizedBox(); width: width,
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.13),
),
);
} else if (isSelected) {
return Container(
height: height,
width: width,
decoration: BoxDecoration(
color:
ColorsManager.spaceColor.withOpacity(0.07),
),
);
}
return const SizedBox.shrink();
}, },
// weekDetectorBuilder: ({
// required date,
// required height,
// required heightPerMinute,
// required minuteSlotSize,
// required width,
// }) {
// return isInRange(date, highlightStart, highlightEnd)
// ? HatchedColumnBackground(
// backgroundColor: ColorsManager.grey800,
// lineColor: ColorsManager.textGray,
// opacity: 0.3,
// stripeSpacing: 12,
// borderRadius: BorderRadius.circular(8),
// )
// : const SizedBox();
// },
pageViewPhysics: const NeverScrollableScrollPhysics(), pageViewPhysics: const NeverScrollableScrollPhysics(),
key: ValueKey(weekStart), key: ValueKey(weekStart),
controller: eventController, controller: eventController,
initialDay: weekStart, initialDay: weekStart,
startHour: startHour - 1, startHour: startHour - 1,
endHour: endHour, endHour: endHour,
heightPerMinute: 1.1, heightPerMinute: 1.7,
showLiveTimeLineInAllDays: false, showLiveTimeLineInAllDays: false,
showVerticalLines: true, showVerticalLines: true,
emulateVerticalOffsetBy: -80, emulateVerticalOffsetBy: -95,
startDay: WeekDays.monday, startDay: WeekDays.monday,
liveTimeIndicatorSettings: const LiveTimeIndicatorSettings( liveTimeIndicatorSettings:
const LiveTimeIndicatorSettings(
showBullet: false, showBullet: false,
height: 0, height: 0,
), ),
@ -122,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(
@ -130,8 +164,12 @@ class WeeklyCalendarPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Text( Text(
firstDayOfWeek.timeZoneName.replaceAll(':00', ''), firstDayOfWeek.timeZoneName
style: Theme.of(context).textTheme.bodyMedium?.copyWith( .replaceAll(':00', ''),
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
fontSize: 12, fontSize: 12,
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -146,36 +184,6 @@ class WeeklyCalendarPage extends StatelessWidget {
); );
}, },
), ),
if (selectedDayIndex >= 0)
Positioned(
left: (timeLineWidth + 3) +
(dayColumnWidth - 8) * (selectedDayIndex - 0.01),
top: 0,
bottom: 0,
width: dayColumnWidth,
child: IgnorePointer(
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 0, horizontal: 4),
color: ColorsManager.spaceColor.withOpacity(0.07),
),
),
),
if (selectedSidebarIndex >= 0 &&
selectedSidebarIndex != selectedDayIndex)
Positioned(
left: (timeLineWidth + 3) +
(dayColumnWidth - 8) * (selectedSidebarIndex - 0.01),
top: 0,
bottom: 0,
width: dayColumnWidth,
child: IgnorePointer(
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 0, horizontal: 4),
color: Colors.orange.withOpacity(0.14),
),
),
), ),
Positioned( Positioned(
right: 0, right: 0,
@ -190,16 +198,11 @@ class WeeklyCalendarPage extends StatelessWidget {
), ),
], ],
), ),
); ),
));
}, },
); );
} }
List<DateTime> _getWeekDays(DateTime date) {
final int weekday = date.weekday;
final DateTime monday = date.subtract(Duration(days: weekday - 1));
return List.generate(7, (i) => monday.add(Duration(days: i)));
}
} }
bool isSameDay(DateTime d1, DateTime d2) { bool isSameDay(DateTime d1, DateTime d2) {

View File

@ -132,6 +132,8 @@ class _DynamicTableState extends State<DynamicTable> {
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _horizontalScrollController, controller: _horizontalScrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
physics:
widget.isEmpty ? const NeverScrollableScrollPhysics() : null,
child: SizedBox( child: SizedBox(
width: _totalTableWidth, width: _totalTableWidth,
child: Column( child: Column(
@ -164,7 +166,6 @@ class _DynamicTableState extends State<DynamicTable> {
], ],
), ),
), ),
Expanded( Expanded(
child: widget.isEmpty child: widget.isEmpty
? _buildEmptyState() ? _buildEmptyState()
@ -265,7 +266,7 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
], ],
), ),
SizedBox(height: widget.size.height * 0.5), SizedBox(height: widget.size.height * 0.2),
], ],
), ),
); );

View File

@ -46,15 +46,16 @@ class DeviceManagementBloc
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (spaceBloc.state.selectedCommunities.isEmpty) { if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi().fetchDevices('', '', projectUuid); devices = await DevicesManagementApi().fetchDevices(
projectUuid,
);
} else { } else {
for (final community in spaceBloc.state.selectedCommunities) { for (var community in spaceBloc.state.selectedCommunities) {
final spacesList = final spacesList =
spaceBloc.state.selectedCommunityAndSpaces[community] ?? []; spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (final space in spacesList) { devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid,
devices.addAll(await DevicesManagementApi() spacesId: spacesList,
.fetchDevices(community, space, projectUuid)); communities: spaceBloc.state.selectedCommunities));
}
} }
} }
@ -158,7 +159,8 @@ class DeviceManagementBloc
add(FilterDevices(_getFilterFromIndex(_selectedIndex))); add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
} }
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) { void _onSelectDevice(
SelectDevice event, Emitter<DeviceManagementState> emit) {
final selectedUuid = event.selectedDevice.uuid; final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
@ -254,7 +256,8 @@ class DeviceManagementBloc
_onlineCount = _devices.where((device) => device.online == true).length; _onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length; _offlineCount = _devices.where((device) => device.online == false).length;
_lowBatteryCount = _devices _lowBatteryCount = _devices
.where((device) => device.batteryLevel != null && device.batteryLevel! < 20) .where((device) =>
device.batteryLevel != null && device.batteryLevel! < 20)
.length; .length;
} }
@ -271,7 +274,8 @@ class DeviceManagementBloc
} }
} }
void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) { void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
if ((event.community == null || event.community!.isEmpty) && if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) &&
(event.deviceNameOrProductName == null || (event.deviceNameOrProductName == null ||
@ -435,8 +439,8 @@ class DeviceManagementBloc
final selectedDevices = loaded.selectedDevice?.map((device) { final selectedDevices = loaded.selectedDevice?.map((device) {
if (device.uuid == event.deviceId) { if (device.uuid == event.deviceId) {
return device.copyWith( return device.copyWith(
subspace: subspace: device.subspace
device.subspace?.copyWith(subspaceName: event.newSubSpaceName)); ?.copyWith(subspaceName: event.newSubSpaceName));
} }
return device; return device;
}).toList(); }).toList();

View File

@ -24,12 +24,12 @@ class DeviceManagementPage extends StatefulWidget with HelperResponsiveLayout {
} }
class _DeviceManagementPageState extends State<DeviceManagementPage> { class _DeviceManagementPageState extends State<DeviceManagementPage> {
@override @override
void initState() { void initState() {
context.read<SpaceTreeBloc>().add(InitialEvent()); context.read<SpaceTreeBloc>().add(InitialEvent());
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
@ -90,7 +90,7 @@ class _DeviceManagementPageState extends State<DeviceManagementPage> {
const TriggerSwitchTabsEvent(isRoutineTab: true)); const TriggerSwitchTabsEvent(isRoutineTab: true));
}, },
child: Text( child: Text(
'Routines', 'Workflow Automation',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: state.routineTab color: state.routineTab
? ColorsManager.whiteColors ? ColorsManager.whiteColors

View File

@ -29,7 +29,9 @@ class CountdownModeButtons extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
elevation: 2.5,
height: 40, height: 40,
borderRadius: 8,
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
backgroundColor: ColorsManager.boxColor, backgroundColor: ColorsManager.boxColor,
child: Text('Cancel', style: context.textTheme.bodyMedium), child: Text('Cancel', style: context.textTheme.bodyMedium),
@ -39,6 +41,8 @@ class CountdownModeButtons extends StatelessWidget {
Expanded( Expanded(
child: isActive child: isActive
? DefaultButton( ? DefaultButton(
elevation: 2.5,
borderRadius: 8,
height: 40, height: 40,
onPressed: () { onPressed: () {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
@ -49,10 +53,12 @@ class CountdownModeButtons extends StatelessWidget {
), ),
); );
}, },
backgroundColor: Colors.red, backgroundColor: ColorsManager.red100,
child: const Text('Stop'), child: const Text('Stop'),
) )
: DefaultButton( : DefaultButton(
elevation: 2.5,
borderRadius: 8,
height: 40, height: 40,
onPressed: () { onPressed: () {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
@ -63,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
countDownCode: countDownCode), countDownCode: countDownCode),
); );
}, },
backgroundColor: ColorsManager.primaryColor, backgroundColor: ColorsManager.primaryColorWithOpacity,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -226,6 +226,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
index.toString().padLeft(2, '0'), index.toString().padLeft(2, '0'),
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w400,
color: isActive ? ColorsManager.grayColor : Colors.black, color: isActive ? ColorsManager.grayColor : Colors.black,
), ),
), ),
@ -240,7 +241,8 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
label, label,
style: const TextStyle( style: const TextStyle(
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
fontSize: 18, fontSize: 24,
fontWeight: FontWeight.w400,
), ),
), ),
], ],

View File

@ -31,12 +31,11 @@ class BuildScheduleView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => ScheduleBloc( create: (_) => ScheduleBloc(deviceId: deviceUuid,)
deviceId: deviceUuid,
)
..add(ScheduleGetEvent(category: category)) ..add(ScheduleGetEvent(category: category))
..add(ScheduleFetchStatusEvent( ..add(ScheduleFetchStatusEvent(
deviceId: deviceUuid, countdownCode: countdownCode ?? '')), deviceId: deviceUuid,
countdownCode: countdownCode ?? '')),
child: Dialog( child: Dialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20), insetPadding: const EdgeInsets.all(20),
@ -77,7 +76,8 @@ class BuildScheduleView extends StatelessWidget {
category: category, category: category,
time: '', time: '',
function: Status( function: Status(
code: code.toString(), value: null), code: code.toString(),
value: true),
days: [], days: [],
), ),
isEdit: false, isEdit: false,

View File

@ -13,9 +13,9 @@ class ScheduleHeader extends StatelessWidget {
Text( Text(
'Scheduling', 'Scheduling',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, color: ColorsManager.primaryColorWithOpacity,
fontSize: 22, fontWeight: FontWeight.w700,
color: ColorsManager.dialogBlueTitle, fontSize: 30,
), ),
), ),
Container( Container(

View File

@ -27,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget {
width: 170, width: 170,
height: 40, height: 40,
child: DefaultButton( child: DefaultButton(
borderColor: ColorsManager.boxColor, borderColor: ColorsManager.grayColor.withOpacity(0.5),
padding: 2, padding: 2,
backgroundColor: ColorsManager.graysColor, backgroundColor: ColorsManager.graysColor,
borderRadius: 15, borderRadius: 15,

View File

@ -19,6 +19,8 @@ class ScheduleModeButtons extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
elevation: 2.5,
borderRadius: 8,
height: 40, height: 40,
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
@ -33,9 +35,11 @@ class ScheduleModeButtons extends StatelessWidget {
const SizedBox(width: 20), const SizedBox(width: 20),
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
elevation: 2.5,
borderRadius: 8,
height: 40, height: 40,
onPressed: onSave, onPressed: onSave,
backgroundColor: ColorsManager.primaryColor, backgroundColor: ColorsManager.primaryColorWithOpacity,
child: const Text('Save'), child: const Text('Save'),
), ),
), ),

View File

@ -35,12 +35,12 @@ class ScheduleModeSelector extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildRadioTile( _buildRadioTile(
context, 'Countdown', ScheduleModes.countdown, currentMode), context, 'Countdown', ScheduleModes.countdown, currentMode),
_buildRadioTile( _buildRadioTile(
context, 'Schedule', ScheduleModes.schedule, currentMode), context, 'Schedule', ScheduleModes.schedule, currentMode),
const Spacer(flex: 1),
// _buildRadioTile( // _buildRadioTile(
// context, 'Circulate', ScheduleModes.circulate, currentMode), // context, 'Circulate', ScheduleModes.circulate, currentMode),
// _buildRadioTile( // _buildRadioTile(
@ -65,6 +65,7 @@ class ScheduleModeSelector extends StatelessWidget {
style: context.textTheme.bodySmall!.copyWith( style: context.textTheme.bodySmall!.copyWith(
fontSize: 13, fontSize: 13,
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
), ),
), ),
leading: Radio<ScheduleModes>( leading: Radio<ScheduleModes>(

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class ScheduleDialogHelper { class ScheduleDialogHelper {
static const List<String> allDays = [ static const List<String> allDays = [
@ -56,8 +58,9 @@ class ScheduleDialogHelper {
Text( Text(
isEdit ? 'Edit Schedule' : 'Add Schedule', isEdit ? 'Edit Schedule' : 'Add Schedule',
style: Theme.of(context).textTheme.titleLarge!.copyWith( style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Colors.blue, color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w700,
fontSize: 30,
), ),
), ),
const SizedBox(), const SizedBox(),
@ -69,9 +72,9 @@ class ScheduleDialogHelper {
height: 40, height: 40,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[200], backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(8),
), ),
), ),
onPressed: () async { onPressed: () async {
@ -110,19 +113,8 @@ class ScheduleDialogHelper {
], ],
), ),
actions: [ actions: [
SizedBox( ScheduleModeButtons(
width: 100, onSave: () {
child: OutlinedButton(
onPressed: () {
Navigator.pop(ctx, null);
},
child: const Text('Cancel'),
),
),
SizedBox(
width: 100,
child: ElevatedButton(
onPressed: () {
dynamic temp; dynamic temp;
if (deviceType == 'CUR_2') { if (deviceType == 'CUR_2') {
temp = functionOn! ? 'open' : 'close'; temp = functionOn! ? 'open' : 'close';
@ -141,8 +133,7 @@ class ScheduleDialogHelper {
); );
Navigator.pop(ctx, entry); Navigator.pop(ctx, entry);
}, },
child: const Text('Save'), ),
)),
], ],
); );
}, },

View File

@ -153,6 +153,7 @@ class EditUserModel {
final String? jobTitle; // can be empty final String? jobTitle; // can be empty
final String roleType; // e.g. "ADMIN" final String roleType; // e.g. "ADMIN"
final List<UserSpaceModel> spaces; final List<UserSpaceModel> spaces;
final String? companyName;
EditUserModel({ EditUserModel({
required this.uuid, required this.uuid,
@ -167,6 +168,7 @@ class EditUserModel {
required this.jobTitle, required this.jobTitle,
required this.roleType, required this.roleType,
required this.spaces, required this.spaces,
this.companyName,
}); });
/// Create a [UserData] from JSON data /// Create a [UserData] from JSON data
@ -182,6 +184,7 @@ class EditUserModel {
invitedBy: json['invitedBy'] as String, invitedBy: json['invitedBy'] as String,
phoneNumber: json['phoneNumber'] ?? '', phoneNumber: json['phoneNumber'] ?? '',
jobTitle: json['jobTitle'] ?? '', jobTitle: json['jobTitle'] ?? '',
companyName: json['companyName'] as String?,
roleType: json['roleType'] as String, roleType: json['roleType'] as String,
spaces: (json['spaces'] as List<dynamic>) spaces: (json['spaces'] as List<dynamic>)
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>)) .map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))

View File

@ -12,7 +12,7 @@ class RolesUserModel {
final dynamic jobTitle; final dynamic jobTitle;
final dynamic createdDate; final dynamic createdDate;
final dynamic createdTime; final dynamic createdTime;
final String? companyName;
RolesUserModel({ RolesUserModel({
required this.uuid, required this.uuid,
required this.createdAt, required this.createdAt,
@ -27,6 +27,7 @@ class RolesUserModel {
this.jobTitle, this.jobTitle,
required this.createdDate, required this.createdDate,
required this.createdTime, required this.createdTime,
this.companyName,
}); });
factory RolesUserModel.fromJson(Map<String, dynamic> json) { factory RolesUserModel.fromJson(Map<String, dynamic> json) {
@ -47,6 +48,7 @@ class RolesUserModel {
: json['jobTitle'], : json['jobTitle'],
createdDate: json['createdDate'], createdDate: json['createdDate'],
createdTime: json['createdTime'], createdTime: json['createdTime'],
companyName: json['companyName'] as String?,
); );
} }
} }

View File

@ -52,7 +52,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
final TextEditingController lastNameController = TextEditingController(); final TextEditingController lastNameController = TextEditingController();
final TextEditingController emailController = TextEditingController(); final TextEditingController emailController = TextEditingController();
final TextEditingController phoneController = TextEditingController(); final TextEditingController phoneController = TextEditingController();
final TextEditingController jobTitleController = TextEditingController(); final TextEditingController companyNameController = TextEditingController();
final TextEditingController roleSearchController = TextEditingController(); final TextEditingController roleSearchController = TextEditingController();
bool? isCompleteBasics; bool? isCompleteBasics;
@ -352,7 +352,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
bool res = await UserPermissionApi().sendInviteUser( bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text, email: emailController.text,
firstName: firstNameController.text, firstName: firstNameController.text,
jobTitle: jobTitleController.text, companyName: companyNameController.text,
lastName: lastNameController.text, lastName: lastNameController.text,
phoneNumber: phoneController.text, phoneNumber: phoneController.text,
roleUuid: roleSelected, roleUuid: roleSelected,
@ -405,7 +405,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
bool res = await UserPermissionApi().editInviteUser( bool res = await UserPermissionApi().editInviteUser(
userId: event.userId, userId: event.userId,
firstName: firstNameController.text, firstName: firstNameController.text,
jobTitle: jobTitleController.text, companyName: companyNameController.text,
lastName: lastNameController.text, lastName: lastNameController.text,
phoneNumber: phoneController.text, phoneNumber: phoneController.text,
roleUuid: roleSelected, roleUuid: roleSelected,
@ -529,7 +529,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
lastNameController.text = res.lastName; lastNameController.text = res.lastName;
emailController.text = res.email; emailController.text = res.email;
phoneController.text = res.phoneNumber ?? ''; phoneController.text = res.phoneNumber ?? '';
jobTitleController.text = res.jobTitle ?? ''; companyNameController.text = res.companyName ?? '';
res.roleType; res.roleType;
res.spaces.map((space) { res.spaces.map((space) {
selectedIds.add(space.uuid); selectedIds.add(space.uuid);
@ -645,7 +645,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
lastNameController.dispose(); lastNameController.dispose();
emailController.dispose(); emailController.dispose();
phoneController.dispose(); phoneController.dispose();
jobTitleController.dispose(); companyNameController.dispose();
roleSearchController.dispose(); roleSearchController.dispose();
return super.close(); return super.close();
} }

View File

@ -317,7 +317,7 @@ class BasicsView extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Text( Text(
'Job Title', 'Company Name',
style: context.textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 13, fontSize: 13,
@ -328,11 +328,11 @@ class BasicsView extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: TextFormField( child: TextFormField(
controller: _blocRole.jobTitleController, controller: _blocRole.companyNameController,
style: style:
const TextStyle(color: ColorsManager.blackColor), const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco( decoration: inputTextFormDeco(
hintText: "Job Title (Optional)") hintText: 'Company Name (Optional)')
.copyWith( .copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith( hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,

View File

@ -411,7 +411,7 @@ class UsersPage extends StatelessWidget {
titles: const [ titles: const [
"Full Name", "Full Name",
"Email Address", "Email Address",
"Job Title", "Company Name",
"Role", "Role",
"Creation Date", "Creation Date",
"Creation Time", "Creation Time",
@ -424,7 +424,7 @@ class UsersPage extends StatelessWidget {
return [ return [
Text('${user.firstName} ${user.lastName}'), Text('${user.firstName} ${user.lastName}'),
Text(user.email), Text(user.email),
Text(user.jobTitle), Center(child: Text(user.companyName ?? '-')),
Text(user.roleType ?? ''), Text(user.roleType ?? ''),
Text(user.createdDate ?? ''), Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''), Text(user.createdTime ?? ''),

View File

@ -936,16 +936,19 @@ Future<void> _onLoadScenes(
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi() devices.addAll(await DevicesManagementApi().fetchDevices(
.fetchDevices(communityId, spaceId, projectUuid)); projectUuid,
} spacesId: spacesList,
communities: spaceBloc.state.selectedCommunities,
));
} }
} else { } else {
devices.addAll(await DevicesManagementApi().fetchDevices( devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId, projectUuid,
createRoutineBloc.selectedSpaceId, spacesId: [createRoutineBloc.selectedSpaceId],
projectUuid)); communities: spaceBloc.state.selectedCommunities,
));
} }
emit(state.copyWith(isLoading: false, devices: devices)); emit(state.copyWith(isLoading: false, devices: devices));

View File

@ -96,9 +96,7 @@ class _WallPresenceSensorState extends State<FlushPresenceSensor> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
DialogHeader(widget.dialogType == 'THEN' const DialogHeader('Presence Sensor'),
? 'Presence Sensor Functions'
: 'Presence Sensor Condition'),
Expanded(child: _buildMainContent(context, state)), Expanded(child: _buildMainContent(context, state)),
_buildDialogFooter(context, state), _buildDialogFooter(context, state),
], ],

View File

@ -12,20 +12,20 @@ import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
class DevicesManagementApi { class DevicesManagementApi {
Future<List<AllDevicesModel>> fetchDevices( Future<List<AllDevicesModel>> fetchDevices(String projectId,
String communityId, String spaceId, String projectId) async { {List<String>? spacesId, List<String>? communities}) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: communityId.isNotEmpty && spaceId.isNotEmpty path: ApiEndpoints.getSpaceDevices.replaceAll('{projectId}', projectId),
? ApiEndpoints.getSpaceDevices queryParameters: {
.replaceAll('{spaceUuid}', spaceId) if (spacesId != null && spacesId.isNotEmpty) 'spaces': spacesId,
.replaceAll('{communityUuid}', communityId) if (communities != null && communities.isNotEmpty)
.replaceAll('{projectId}', projectId) 'communities': communities,
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), },
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = json['data']; final List<dynamic> jsonData = json['data'] as List<dynamic>;
List<AllDevicesModel> devicesList = jsonData.map((jsonItem) { final List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
return AllDevicesModel.fromJson(jsonItem); return AllDevicesModel.fromJson(jsonItem);
}).toList(); }).toList();
return devicesList; return devicesList;
@ -416,5 +416,4 @@ class DevicesManagementApi {
); );
return response; return response;
} }
} }

View File

@ -34,8 +34,9 @@ class UserPermissionApi {
path: ApiEndpoints.roleTypes, path: ApiEndpoints.roleTypes,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
final List<RoleTypeModel> fetchedRoles = final List<RoleTypeModel> fetchedRoles = (json['data'] as List)
(json['data'] as List).map((item) => RoleTypeModel.fromJson(item)).toList(); .map((item) => RoleTypeModel.fromJson(item))
.toList();
return fetchedRoles; return fetchedRoles;
}, },
); );
@ -47,7 +48,9 @@ class UserPermissionApi {
path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid), path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return (json as List).map((data) => PermissionOption.fromJson(data)).toList(); return (json as List)
.map((data) => PermissionOption.fromJson(data))
.toList();
}, },
); );
return response ?? []; return response ?? [];
@ -57,7 +60,7 @@ class UserPermissionApi {
String? firstName, String? firstName,
String? lastName, String? lastName,
String? email, String? email,
String? jobTitle, String? companyName,
String? phoneNumber, String? phoneNumber,
String? roleUuid, String? roleUuid,
List<String>? spaceUuids, List<String>? spaceUuids,
@ -68,7 +71,7 @@ class UserPermissionApi {
"firstName": firstName, "firstName": firstName,
"lastName": lastName, "lastName": lastName,
"email": email, "email": email,
"jobTitle": jobTitle != '' ? jobTitle : null, "companyName": companyName != '' ? companyName : null,
"phoneNumber": phoneNumber != '' ? phoneNumber : null, "phoneNumber": phoneNumber != '' ? phoneNumber : null,
"roleUuid": roleUuid, "roleUuid": roleUuid,
"projectUuid": projectUuid, "projectUuid": projectUuid,
@ -140,7 +143,7 @@ class UserPermissionApi {
String? firstName, String? firstName,
String? userId, String? userId,
String? lastName, String? lastName,
String? jobTitle, String? companyName,
String? phoneNumber, String? phoneNumber,
String? roleUuid, String? roleUuid,
List<String>? spaceUuids, List<String>? spaceUuids,
@ -150,8 +153,8 @@ class UserPermissionApi {
final body = <String, dynamic>{ final body = <String, dynamic>{
"firstName": firstName, "firstName": firstName,
"lastName": lastName, "lastName": lastName,
"jobTitle": jobTitle != '' ? jobTitle : " ", "companyName": companyName != '' ? companyName : ' ',
"phoneNumber": phoneNumber != '' ? phoneNumber : " ", "phoneNumber": phoneNumber != '' ? phoneNumber : ' ',
"roleUuid": roleUuid, "roleUuid": roleUuid,
"projectUuid": projectUuid, "projectUuid": projectUuid,
"spaceUuids": spaceUuids, "spaceUuids": spaceUuids,
@ -190,12 +193,17 @@ class UserPermissionApi {
} }
} }
Future<bool> changeUserStatusById(userUuid, status, String projectUuid) async { Future<bool> changeUserStatusById(
userUuid, status, String projectUuid) async {
try { try {
Map<String, dynamic> bodya = {"disable": status, "projectUuid": projectUuid}; Map<String, dynamic> bodya = {
"disable": status,
"projectUuid": projectUuid
};
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.changeUserStatus.replaceAll("{invitedUserUuid}", userUuid), path: ApiEndpoints.changeUserStatus
.replaceAll("{invitedUserUuid}", userUuid),
body: bodya, body: bodya,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json['success']; return json['success'];

View File

@ -84,5 +84,6 @@ abstract class ColorsManager {
static const Color minBlueDot = Color(0xFF023DFE); static const Color minBlueDot = Color(0xFF023DFE);
static const Color grey25 = Color(0xFFF9F9F9); static const Color grey25 = Color(0xFFF9F9F9);
static const Color grey50 = Color(0xFF718096); static const Color grey50 = Color(0xFF718096);
static const Color red100 = Color(0xFFFE0202);
static const Color grey800 = Color(0xffF8F8F8); static const Color grey800 = Color(0xffF8F8F8);
} }

View File

@ -17,8 +17,7 @@ abstract class ApiEndpoints {
////// Devices Management //////////////// ////// Devices Management ////////////////
static const String getAllDevices = '/projects/{projectId}/devices'; static const String getAllDevices = '/projects/{projectId}/devices';
static const String getSpaceDevices = static const String getSpaceDevices = '/projects/{projectId}/devices';
'/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
static const String getDeviceStatus = '/devices/{uuid}/functions/status'; static const String getDeviceStatus = '/devices/{uuid}/functions/status';
static const String getBatchStatus = '/devices/batch'; static const String getBatchStatus = '/devices/batch';
@ -141,5 +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 getCalendarEvents = '/api'; static const String getBookings = '/bookings?month={mm}-{yyyy}&space={space}';
} }