mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 14:47:23 +00:00
Implement-Calendar-ui
This commit is contained in:
@ -0,0 +1,143 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
part 'events_event.dart';
|
||||||
|
part 'events_state.dart';
|
||||||
|
|
||||||
|
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||||
|
final EventController eventController = EventController();
|
||||||
|
|
||||||
|
CalendarEventsBloc() : super(EventsInitial()) {
|
||||||
|
on<LoadEvents>(_onLoadEvents);
|
||||||
|
on<AddEvent>(_onAddEvent);
|
||||||
|
on<StartTimer>(_onStartTimer);
|
||||||
|
on<DisposeResources>(_onDisposeResources);
|
||||||
|
on<GoToWeek>(_onGoToWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadEvents(
|
||||||
|
LoadEvents event,
|
||||||
|
Emitter<CalendarEventState> emit,
|
||||||
|
) async {
|
||||||
|
emit(EventsLoading());
|
||||||
|
try {
|
||||||
|
final events = _generateDummyEventsForWeek(event.weekStart);
|
||||||
|
eventController.addAll(events);
|
||||||
|
emit(EventsLoaded(
|
||||||
|
events: events,
|
||||||
|
initialDate: event.weekStart,
|
||||||
|
weekDays: _getWeekDays(event.weekStart),
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
emit(EventsError('Failed to load events'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CalendarEventData> _generateDummyEventsForWeek(DateTime weekStart) {
|
||||||
|
final events = <CalendarEventData>[];
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
final date = weekStart.add(Duration(days: i));
|
||||||
|
|
||||||
|
events.add(CalendarEventData(
|
||||||
|
date: date,
|
||||||
|
startTime: date.copyWith(hour: 9, minute: 0),
|
||||||
|
endTime: date.copyWith(hour: 10, minute: 30),
|
||||||
|
title: 'Team Meeting',
|
||||||
|
description: 'Daily standup',
|
||||||
|
color: Colors.blue,
|
||||||
|
));
|
||||||
|
events.add(CalendarEventData(
|
||||||
|
date: date,
|
||||||
|
startTime: date.copyWith(hour: 14, minute: 0),
|
||||||
|
endTime: date.copyWith(hour: 15, minute: 0),
|
||||||
|
title: 'Client Call',
|
||||||
|
description: 'Project discussion',
|
||||||
|
color: Colors.green,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
|
||||||
|
eventController.add(event.event);
|
||||||
|
if (state is EventsLoaded) {
|
||||||
|
final loaded = state as EventsLoaded;
|
||||||
|
emit(EventsLoaded(
|
||||||
|
events: [...eventController.events],
|
||||||
|
initialDate: loaded.initialDate,
|
||||||
|
weekDays: loaded.weekDays,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onStartTimer(StartTimer event, Emitter<CalendarEventState> emit) {}
|
||||||
|
|
||||||
|
void _onDisposeResources(
|
||||||
|
DisposeResources event, Emitter<CalendarEventState> emit) {
|
||||||
|
eventController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onGoToWeek(GoToWeek event, Emitter<CalendarEventState> emit) {
|
||||||
|
if (state is EventsLoaded) {
|
||||||
|
final loaded = state as EventsLoaded;
|
||||||
|
final newWeekDays = _getWeekDays(event.weekDate);
|
||||||
|
emit(EventsLoaded(
|
||||||
|
events: loaded.events,
|
||||||
|
initialDate: event.weekDate,
|
||||||
|
weekDays: newWeekDays,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CalendarEventData> _generateDummyEvents() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
return [
|
||||||
|
CalendarEventData(
|
||||||
|
date: now,
|
||||||
|
startTime: now.copyWith(hour: 8, minute: 00, second: 0),
|
||||||
|
endTime: now.copyWith(hour: 9, minute: 00, second: 0),
|
||||||
|
title: 'Team Meeting',
|
||||||
|
description: 'Weekly team sync',
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
CalendarEventData(
|
||||||
|
date: now,
|
||||||
|
startTime: now.copyWith(hour: 9, minute: 00, second: 0),
|
||||||
|
endTime: now.copyWith(hour: 10, minute: 30, second: 0),
|
||||||
|
title: 'Team Meeting',
|
||||||
|
description: 'Weekly team sync',
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
CalendarEventData(
|
||||||
|
date: now.add(const Duration(days: 1)),
|
||||||
|
startTime: now.copyWith(hour: 14, day: now.day + 1),
|
||||||
|
endTime: now.copyWith(hour: 15, day: now.day + 1),
|
||||||
|
title: 'Client Call',
|
||||||
|
description: 'Project discussion',
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
CalendarEventData(
|
||||||
|
date: now.add(const Duration(days: 2)),
|
||||||
|
startTime: now.copyWith(hour: 11, day: now.day + 2),
|
||||||
|
endTime: now.copyWith(hour: 12, day: now.day + 2),
|
||||||
|
title: 'Lunch with Team',
|
||||||
|
color: Colors.orange,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
eventController.dispose();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
part of 'events_bloc.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
abstract class CalendarEventsEvent {
|
||||||
|
const CalendarEventsEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadEvents extends CalendarEventsEvent {
|
||||||
|
final DateTime weekStart;
|
||||||
|
const LoadEvents({required this.weekStart});
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddEvent extends CalendarEventsEvent {
|
||||||
|
final CalendarEventData event;
|
||||||
|
AddEvent(this.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StartTimer extends CalendarEventsEvent {}
|
||||||
|
|
||||||
|
class DisposeResources extends CalendarEventsEvent {}
|
||||||
|
|
||||||
|
class GoToWeek extends CalendarEventsEvent {
|
||||||
|
final DateTime weekDate;
|
||||||
|
GoToWeek(this.weekDate);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
part of 'events_bloc.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
abstract class CalendarEventState {}
|
||||||
|
|
||||||
|
class EventsInitial extends CalendarEventState {}
|
||||||
|
|
||||||
|
class EventsLoading extends CalendarEventState {}
|
||||||
|
|
||||||
|
class EventsLoaded extends CalendarEventState {
|
||||||
|
final List<CalendarEventData> events;
|
||||||
|
final DateTime initialDate;
|
||||||
|
final List<DateTime> weekDays;
|
||||||
|
|
||||||
|
EventsLoaded({
|
||||||
|
required this.events,
|
||||||
|
required this.initialDate,
|
||||||
|
required this.weekDays,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventsError extends CalendarEventState {
|
||||||
|
final String message;
|
||||||
|
EventsError(this.message);
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/date_selection/date_selection_event.dart';
|
||||||
|
import 'date_selection_state.dart';
|
||||||
|
|
||||||
|
class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
||||||
|
DateSelectionBloc() : super(DateSelectionState.initial()) {
|
||||||
|
on<SelectDate>((event, emit) {
|
||||||
|
final newWeekStart = _getStartOfWeek(event.selectedDate);
|
||||||
|
emit(DateSelectionState(
|
||||||
|
selectedDate: event.selectedDate,
|
||||||
|
weekStart: newWeekStart,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
on<NextWeek>((event, emit) {
|
||||||
|
final newWeekStart = state.weekStart.add(const Duration(days: 7));
|
||||||
|
final inNewWeek = state.selectedDate
|
||||||
|
.isAfter(newWeekStart.subtract(const Duration(days: 1))) &&
|
||||||
|
state.selectedDate
|
||||||
|
.isBefore(newWeekStart.add(const Duration(days: 7)));
|
||||||
|
emit(DateSelectionState(
|
||||||
|
selectedDate: state.selectedDate,
|
||||||
|
weekStart: newWeekStart,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
on<PreviousWeek>((event, emit) {
|
||||||
|
emit(DateSelectionState(
|
||||||
|
selectedDate: state.selectedDate!.subtract(const Duration(days: 7)),
|
||||||
|
weekStart: state.weekStart.subtract(const Duration(days: 7)),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime _getStartOfWeek(DateTime date) {
|
||||||
|
return date.subtract(Duration(days: date.weekday - 1));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
abstract class DateSelectionEvent {
|
||||||
|
const DateSelectionEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectDate extends DateSelectionEvent {
|
||||||
|
final DateTime selectedDate;
|
||||||
|
const SelectDate(this.selectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NextWeek extends DateSelectionEvent {}
|
||||||
|
|
||||||
|
class PreviousWeek extends DateSelectionEvent {}
|
@ -0,0 +1,21 @@
|
|||||||
|
class DateSelectionState {
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final DateTime weekStart;
|
||||||
|
|
||||||
|
const DateSelectionState({
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.weekStart,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory DateSelectionState.initial() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
return DateSelectionState(
|
||||||
|
selectedDate: now,
|
||||||
|
weekStart: _getStartOfWeek(now),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime _getStartOfWeek(DateTime date) {
|
||||||
|
return date.subtract(Duration(days: date.weekday - 1));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
part 'selected_bookable_space_event.dart';
|
||||||
|
part 'selected_bookable_space_state.dart';
|
||||||
|
|
||||||
|
class SelectedBookableSpaceBloc
|
||||||
|
extends Bloc<SelectedBookableSpaceEvent, SelectedBookableSpaceState> {
|
||||||
|
SelectedBookableSpaceBloc() : super(const SelectedBookableSpaceState()) {
|
||||||
|
on<SelectBookableSpace>((event, emit) {
|
||||||
|
emit(SelectedBookableSpaceState(selectedSpaceId: event.spaceId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
part of 'selected_bookable_space_bloc.dart';
|
||||||
|
|
||||||
|
abstract class SelectedBookableSpaceEvent {
|
||||||
|
const SelectedBookableSpaceEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectBookableSpace extends SelectedBookableSpaceEvent {
|
||||||
|
final dynamic spaceId;
|
||||||
|
|
||||||
|
const SelectBookableSpace(this.spaceId);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
part of 'selected_bookable_space_bloc.dart';
|
||||||
|
|
||||||
|
class SelectedBookableSpaceState {
|
||||||
|
final String? selectedSpaceId;
|
||||||
|
|
||||||
|
const SelectedBookableSpaceState({this.selectedSpaceId});
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/sidebar/sidebar_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/sidebar/sidebar_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/model/bookable_room.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
class SidebarBloc extends Bloc<SidebarEvent, SidebarState> {
|
||||||
|
SidebarBloc() : super(SidebarState(
|
||||||
|
allRooms: [],
|
||||||
|
displayedRooms: [],
|
||||||
|
isLoading: true,
|
||||||
|
)) {
|
||||||
|
on<LoadRoomsEvent>(_onLoadRooms);
|
||||||
|
on<SelectRoomEvent>(_onSelectRoom);
|
||||||
|
on<SearchRoomsEvent>(_onSearchRooms);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadRooms(
|
||||||
|
LoadRoomsEvent event,
|
||||||
|
Emitter<SidebarState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
final rooms = List.generate(15, (index) => BookableRoom(
|
||||||
|
id: index,
|
||||||
|
name: 'Meeting Room ${index + 1}',
|
||||||
|
capacity: [4, 6, 8, 10][index % 4],
|
||||||
|
iconAsset: Assets.AtoZIcon,
|
||||||
|
));
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
allRooms: rooms,
|
||||||
|
displayedRooms: rooms,
|
||||||
|
isLoading: false,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: 'Failed to load rooms',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSelectRoom(
|
||||||
|
SelectRoomEvent event,
|
||||||
|
Emitter<SidebarState> emit,
|
||||||
|
) {
|
||||||
|
emit(state.copyWith(selectedRoomId: event.roomId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearchRooms(
|
||||||
|
SearchRoomsEvent event,
|
||||||
|
Emitter<SidebarState> emit,
|
||||||
|
) {
|
||||||
|
if (event.query.isEmpty) {
|
||||||
|
emit(state.copyWith(displayedRooms: state.allRooms));
|
||||||
|
} else {
|
||||||
|
final filtered = state.allRooms.where((room) =>
|
||||||
|
room.name.toLowerCase().contains(event.query.toLowerCase())).toList();
|
||||||
|
emit(state.copyWith(displayedRooms: filtered));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
abstract class SidebarEvent {}
|
||||||
|
|
||||||
|
class LoadRoomsEvent extends SidebarEvent {}
|
||||||
|
|
||||||
|
class SelectRoomEvent extends SidebarEvent {
|
||||||
|
final int roomId;
|
||||||
|
|
||||||
|
SelectRoomEvent(this.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchRoomsEvent extends SidebarEvent {
|
||||||
|
final String query;
|
||||||
|
|
||||||
|
SearchRoomsEvent(this.query);
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/model/bookable_room.dart';
|
||||||
|
|
||||||
|
class SidebarState {
|
||||||
|
final List<BookableRoom> allRooms;
|
||||||
|
final List<BookableRoom> displayedRooms;
|
||||||
|
final int? selectedRoomId;
|
||||||
|
final bool isLoading;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
SidebarState({
|
||||||
|
required this.allRooms,
|
||||||
|
required this.displayedRooms,
|
||||||
|
this.selectedRoomId,
|
||||||
|
this.isLoading = false,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
SidebarState copyWith({
|
||||||
|
List<BookableRoom>? allRooms,
|
||||||
|
List<BookableRoom>? displayedRooms,
|
||||||
|
int? selectedRoomId,
|
||||||
|
bool? isLoading,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return SidebarState(
|
||||||
|
allRooms: allRooms ?? this.allRooms,
|
||||||
|
displayedRooms: displayedRooms ?? this.displayedRooms,
|
||||||
|
selectedRoomId: selectedRoomId ?? this.selectedRoomId,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
class BookableRoom {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final int capacity;
|
||||||
|
final String? iconAsset;
|
||||||
|
|
||||||
|
BookableRoom({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
this.capacity = 4,
|
||||||
|
this.iconAsset,
|
||||||
|
});
|
||||||
|
}
|
@ -1,52 +1,235 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/date_selection/date_selection_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/date_selection/date_selection_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/date_selection/date_selection_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/selected_bookable_space_bloc/selected_bookable_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/booking_sidebar.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/custom_calendar_page.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/weekly_calendar_page.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
class BookingPage extends StatelessWidget {
|
class BookingPage extends StatefulWidget {
|
||||||
const BookingPage({super.key});
|
const BookingPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BookingPage> createState() => _BookingPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BookingPageState extends State<BookingPage> {
|
||||||
|
late final EventController _eventController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_eventController = EventController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_eventController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CalendarEventData> _generateDummyEventsForWeek(DateTime weekStart) {
|
||||||
|
final List<CalendarEventData> events = [];
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
final date = weekStart.add(Duration(days: i));
|
||||||
|
events.add(CalendarEventData(
|
||||||
|
date: date,
|
||||||
|
startTime: date.copyWith(hour: 9, minute: 0),
|
||||||
|
endTime: date.copyWith(hour: 10, minute: 30),
|
||||||
|
title: 'Team Meeting',
|
||||||
|
description: 'Daily standup',
|
||||||
|
color: Colors.blue,
|
||||||
|
));
|
||||||
|
events.add(CalendarEventData(
|
||||||
|
date: date,
|
||||||
|
startTime: date.copyWith(hour: 14, minute: 0),
|
||||||
|
endTime: date.copyWith(hour: 15, minute: 0),
|
||||||
|
title: 'Client Call',
|
||||||
|
description: 'Project discussion',
|
||||||
|
color: Colors.green,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadEventsForWeek(DateTime weekStart) {
|
||||||
|
_eventController.removeWhere((_) => true);
|
||||||
|
_eventController.addAll(_generateDummyEventsForWeek(weekStart));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return MultiBlocProvider(
|
||||||
child: Row(
|
providers: [
|
||||||
children: [
|
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
||||||
Expanded(
|
BlocProvider(create: (_) => DateSelectionBloc()),
|
||||||
child: Container(
|
],
|
||||||
color: Colors.blueGrey[100],
|
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
||||||
child: const Center(
|
listenWhen: (previous, current) =>
|
||||||
child: Text(
|
previous.weekStart != current.weekStart,
|
||||||
'Side bar',
|
listener: (context, state) {
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
_loadEventsForWeek(state.weekStart);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||||
|
SelectedBookableSpaceState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return BookingSidebar(
|
||||||
|
onRoomSelected: (id) {
|
||||||
|
context
|
||||||
|
.read<SelectedBookableSpaceBloc>()
|
||||||
|
.add(SelectBookableSpace(id));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||||
|
builder: (context, dateState) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
child: CustomCalendarPage(
|
||||||
|
selectedDate: dateState.selectedDate,
|
||||||
|
onDateChanged: (day, month, year) {
|
||||||
|
final newDate = DateTime(year, month, day);
|
||||||
|
context
|
||||||
|
.read<DateSelectionBloc>()
|
||||||
|
.add(SelectDate(newDate));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
Expanded(
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
flex: 4,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: SizedBox(
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
SvgTextButton(
|
Row(
|
||||||
|
children: [
|
||||||
|
SvgTextButton(
|
||||||
svgAsset: Assets.homeIcon,
|
svgAsset: Assets.homeIcon,
|
||||||
label: 'Manage Bookable Spaces',
|
label: 'Manage Bookable Spaces',
|
||||||
onPressed: () {}),
|
onPressed: () {},
|
||||||
SizedBox(width: 20),
|
),
|
||||||
SvgTextButton(
|
const SizedBox(width: 20),
|
||||||
|
SvgTextButton(
|
||||||
svgAsset: Assets.groupIcon,
|
svgAsset: Assets.groupIcon,
|
||||||
label: 'Manage Users',
|
label: 'Manage Users',
|
||||||
onPressed: () {})
|
onPressed: () {},
|
||||||
],
|
),
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final weekStart = state.weekStart;
|
||||||
|
final weekEnd =
|
||||||
|
weekStart.add(const Duration(days: 6));
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.circleRolesBackground,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios,
|
||||||
|
color: Colors.black),
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<DateSelectionBloc>()
|
||||||
|
.add(PreviousWeek());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_getMonthYearText(weekStart, weekEnd),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_forward_ios,
|
||||||
|
color: Colors.black),
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<DateSelectionBloc>()
|
||||||
|
.add(NextWeek());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||||
|
builder: (context, dateState) {
|
||||||
|
return WeeklyCalendarPage(
|
||||||
|
weekStart: dateState.weekStart,
|
||||||
|
selectedDate: dateState.selectedDate,
|
||||||
|
eventController: _eventController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getMonthYearText(DateTime start, DateTime end) {
|
||||||
|
final startMonth = DateFormat('MMM').format(start);
|
||||||
|
final endMonth = DateFormat('MMM').format(end);
|
||||||
|
final year = start.year == end.year
|
||||||
|
? start.year.toString()
|
||||||
|
: '${start.year}-${end.year}';
|
||||||
|
|
||||||
|
if (start.month == end.month) {
|
||||||
|
return '$startMonth $year';
|
||||||
|
} else {
|
||||||
|
return '$startMonth - $endMonth $year';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,182 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/sidebar/sidebar_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/sidebar/sidebar_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/bloc/sidebar/sidebar_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/room_list_item.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
class BookingSidebar extends StatelessWidget {
|
||||||
|
final void Function(int) onRoomSelected;
|
||||||
|
|
||||||
|
const BookingSidebar({
|
||||||
|
super.key,
|
||||||
|
required this.onRoomSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => SidebarBloc()..add(LoadRoomsEvent()),
|
||||||
|
child: _SidebarContent(onRoomSelected: onRoomSelected),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SidebarContent extends StatelessWidget {
|
||||||
|
final void Function(int) onRoomSelected;
|
||||||
|
|
||||||
|
const _SidebarContent({
|
||||||
|
required this.onRoomSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||||
|
offset: const Offset(3, 0),
|
||||||
|
blurRadius: 6,
|
||||||
|
spreadRadius: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: BlocBuilder<SidebarBloc, SidebarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const _SidebarHeader(title: 'Spaces'),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||||
|
offset: const Offset(0, -2),
|
||||||
|
blurRadius: 4,
|
||||||
|
spreadRadius: 0,
|
||||||
|
),
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
blurRadius: 4,
|
||||||
|
spreadRadius: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Container(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0, vertical: 8.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.counterBackgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: (value) {
|
||||||
|
context
|
||||||
|
.read<SidebarBloc>()
|
||||||
|
.add(SearchRoomsEvent(value));
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Search',
|
||||||
|
suffixIcon: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.searchIconUser,
|
||||||
|
color: ColorsManager.primaryTextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8, horizontal: 12),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.isLoading)
|
||||||
|
const Expanded(
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
)
|
||||||
|
else if (state.errorMessage != null)
|
||||||
|
Expanded(
|
||||||
|
child: Center(child: Text(state.errorMessage!)),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: state.displayedRooms.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final room = state.displayedRooms[index];
|
||||||
|
return RoomListItem(
|
||||||
|
room: room,
|
||||||
|
isSelected: state.selectedRoomId == room.id,
|
||||||
|
onTap: () {
|
||||||
|
context
|
||||||
|
.read<SidebarBloc>()
|
||||||
|
.add(SelectRoomEvent(room.id));
|
||||||
|
onRoomSelected(room.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SidebarHeader extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
const _SidebarHeader({
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.primaryTextColor,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomCalendarPage extends StatefulWidget {
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final Function(int day, int month, int year) onDateChanged;
|
||||||
|
|
||||||
|
const CustomCalendarPage({
|
||||||
|
super.key,
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.onDateChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomCalendarPage> createState() => _CustomCalendarPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomCalendarPageState extends State<CustomCalendarPage> {
|
||||||
|
late DateTime _selectedDate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_selectedDate = widget.selectedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(CustomCalendarPage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.selectedDate != oldWidget.selectedDate) {
|
||||||
|
setState(() {
|
||||||
|
_selectedDate = widget.selectedDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CalendarDatePicker(
|
||||||
|
initialDate: _selectedDate,
|
||||||
|
firstDate: DateTime(2000),
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
onDateChanged: (date) {
|
||||||
|
widget.onDateChanged(date.day, date.month, date.year);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/model/bookable_room.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class RoomListItem extends StatelessWidget {
|
||||||
|
final BookableRoom room;
|
||||||
|
final bool isSelected;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const RoomListItem({
|
||||||
|
required this.room,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
hoverColor: ColorsManager.primaryColor.withOpacity(0.05),
|
||||||
|
child: Container(
|
||||||
|
height: 40,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IgnorePointer(
|
||||||
|
child: Radio<int>(
|
||||||
|
value: room.id,
|
||||||
|
groupValue: isSelected ? room.id : null,
|
||||||
|
onChanged: (value) {},
|
||||||
|
activeColor: ColorsManager.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
room.name,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
fontWeight:
|
||||||
|
isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class WeeklyCalendarPage extends StatelessWidget {
|
||||||
|
final DateTime weekStart;
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final EventController eventController;
|
||||||
|
|
||||||
|
const WeeklyCalendarPage({
|
||||||
|
super.key,
|
||||||
|
required this.weekStart,
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.eventController,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final weekDays = _getWeekDays(weekStart);
|
||||||
|
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final double calendarWidth = constraints.maxWidth;
|
||||||
|
const double timeLineWidth = 80;
|
||||||
|
const int totalDays = 7;
|
||||||
|
final double dayColumnWidth =
|
||||||
|
(calendarWidth - timeLineWidth) / totalDays;
|
||||||
|
final selectedDayIndex = (selectedDate != null)
|
||||||
|
? weekDays.indexWhere((d) => isSameDay(d, selectedDate))
|
||||||
|
: -1;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
WeekView(
|
||||||
|
key: ValueKey(weekStart),
|
||||||
|
controller: eventController,
|
||||||
|
initialDay: weekStart,
|
||||||
|
startHour: 7,
|
||||||
|
endHour: 18,
|
||||||
|
heightPerMinute: 1.1,
|
||||||
|
showLiveTimeLineInAllDays: false,
|
||||||
|
showVerticalLines: true,
|
||||||
|
emulateVerticalOffsetBy: -80,
|
||||||
|
startDay: WeekDays.monday,
|
||||||
|
liveTimeIndicatorSettings: const LiveTimeIndicatorSettings(
|
||||||
|
showBullet: false,
|
||||||
|
height: 0,
|
||||||
|
),
|
||||||
|
weekDayBuilder: (date) {
|
||||||
|
final weekDays = _getWeekDays(weekStart);
|
||||||
|
final selectedDayIndex =
|
||||||
|
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
||||||
|
final index = weekDays.indexWhere((d) => isSameDay(d, date));
|
||||||
|
final isSelectedDay = index == selectedDayIndex;
|
||||||
|
final isToday = isSameDay(date, DateTime.now());
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: isSelectedDay
|
||||||
|
? BoxDecoration(
|
||||||
|
color: ColorsManager.blue1.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
)
|
||||||
|
: isToday
|
||||||
|
? BoxDecoration(
|
||||||
|
color: ColorsManager.blue1.withOpacity(0.08),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('EEE').format(date).toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
color: isSelectedDay ? Colors.blue : Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
DateFormat('d').format(date),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 20,
|
||||||
|
color: isSelectedDay
|
||||||
|
? ColorsManager.blue1
|
||||||
|
: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
timeLineBuilder: (date) {
|
||||||
|
int hour = date.hour == 0
|
||||||
|
? 12
|
||||||
|
: (date.hour > 12 ? date.hour - 12 : date.hour);
|
||||||
|
String period = date.hour >= 12 ? 'PM' : 'AM';
|
||||||
|
return Container(
|
||||||
|
height: 60,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '$hour',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 24,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 2, top: 6),
|
||||||
|
child: Text(
|
||||||
|
period,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: PlaceholderAlignment.baseline,
|
||||||
|
baseline: TextBaseline.alphabetic,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
timeLineWidth: timeLineWidth,
|
||||||
|
weekPageHeaderBuilder: (start, end) => Container(),
|
||||||
|
weekTitleHeight: 60,
|
||||||
|
weekNumberBuilder: (firstDayOfWeek) => Text(
|
||||||
|
firstDayOfWeek.timeZoneName,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventTileBuilder: (date, events, boundary, start, end) {
|
||||||
|
return Container(
|
||||||
|
margin:
|
||||||
|
const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: events.map((event) {
|
||||||
|
final bool isEventEnded = event.endTime != null &&
|
||||||
|
event.endTime!.isBefore(DateTime.now());
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isEventEnded
|
||||||
|
? ColorsManager.grayColor
|
||||||
|
: ColorsManager.lightGrayColor
|
||||||
|
.withOpacity(0.25),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('h:mm a').format(event.startTime!),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
event.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Highlight the selected day column
|
||||||
|
if (selectedDayIndex >= 0)
|
||||||
|
Positioned(
|
||||||
|
left: timeLineWidth + dayColumnWidth * selectedDayIndex,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: dayColumnWidth,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 0, horizontal: 2),
|
||||||
|
color: ColorsManager.blue1.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 50,
|
||||||
|
bottom: 0,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
width: 1,
|
||||||
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return d1.year == d2.year && d1.month == d2.month && d1.day == d2.day;
|
||||||
|
}
|
@ -63,6 +63,9 @@ dependencies:
|
|||||||
bloc: ^9.0.0
|
bloc: ^9.0.0
|
||||||
geocoding: ^4.0.0
|
geocoding: ^4.0.0
|
||||||
gauge_indicator: ^0.4.3
|
gauge_indicator: ^0.4.3
|
||||||
|
# syncfusion_flutter_calendar: ^30.1.38
|
||||||
|
calendar_view: ^1.4.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
Reference in New Issue
Block a user