feat: Refactor code structure and optimize performance across multiple modules

This commit is contained in:
Achintha Isuru
2025-11-17 23:29:28 -05:00
parent 831570f2e0
commit a64cbd9edf
1508 changed files with 105319 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
const String getStaffScheduleSchema = '''
query GetStaffSchedule {
me {
id
schedule {
id
type
start_at
end_at
day_of_week
}
}
}
''';
const String createStaffScheduleMutationSchema = r'''
mutation CreateStaffSchedule($input: [StaffScheduleInput!]!) {
create_staff_schedule(input: $input) {
id
type
start_at
end_at
day_of_week
}
}
''';
const String updateStaffScheduleMutationSchema = r'''
mutation UpdateStaffSchedule($id: ID!, $input: StaffScheduleInput!) {
update_staff_schedule(id: $id, input: $input) {
id
type
start_at
end_at
day_of_week
}
}
''';
const String deleteStaffScheduleMutationSchema = r'''
mutation DeleteStaffSchedule($ids: [ID!]!) {
delete_staff_schedule(ids: $ids) {
id
type
start_at
end_at
day_of_week
}
}
''';

View File

@@ -0,0 +1,13 @@
import 'package:krow/features/profile/schedule/data/models/schedule_slot_model.dart';
class DayScheduleModel {
DayScheduleModel({
required this.date,
required this.slots,
this.isWeekly = false,
});
final DateTime date;
final bool isWeekly;
List<ScheduleSlotModel> slots;
}

View File

@@ -0,0 +1,38 @@
import 'package:krow/core/application/common/date_time_extension.dart';
class ScheduleSlotModel {
ScheduleSlotModel({
required this.isWeekly,
required this.startAt,
required this.endAt,
this.id,
});
factory ScheduleSlotModel.fromJson(Map<String, dynamic> data) {
return ScheduleSlotModel(
id: data['id'] as String?,
isWeekly: data['type'] == 'weekly',
startAt: DateTime.parse(data['start_at'] as String),
endAt: DateTime.parse(data['end_at'] as String),
);
}
final String? id;
final bool isWeekly;
final DateTime startAt;
final DateTime endAt;
// TODO(Sleep): For now has to use .replaceAll('.000', '') for trim DateTime otherwise will result in error from backend.
Map<String, dynamic> toJson() {
return {
// if (id != null) 'id': id,
'type': isWeekly ? 'weekly' : 'range',
'start_at': startAt.toString().replaceAll('.000', ''),
'end_at': endAt.toString().replaceAll('.000', ''),
'day_of_week': startAt.getWeekdayId(),
};
}
String getIdKey() =>
isWeekly ? startAt.getWeekdayId() : startAt.getDayDateId();
}

View File

@@ -0,0 +1,180 @@
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:injectable/injectable.dart';
import 'package:krow/core/application/clients/api/api_client.dart';
import 'package:krow/features/profile/schedule/data/gql_schemas.dart';
import 'package:krow/features/profile/schedule/data/models/day_schedule_model.dart';
import 'package:krow/features/profile/schedule/data/models/schedule_slot_model.dart';
@injectable
class StaffScheduleApiProvider {
final ApiClient _apiClient;
StaffScheduleApiProvider(this._apiClient);
Map<String, DayScheduleModel> _processScheduleDataList(
List<dynamic> scheduleData,
) {
final schedules = [
for (final scheduleItem in scheduleData)
ScheduleSlotModel.fromJson(
scheduleItem as Map<String, dynamic>,
),
];
Map<String, DayScheduleModel> scheduleMap = {};
for (int i = 0; i < schedules.length; i++) {
final scheduleKey = schedules[i].getIdKey();
var scheduleDay = scheduleMap[scheduleKey];
if (scheduleDay == null) {
scheduleDay = DayScheduleModel(
date: schedules[i].startAt.copyWith(
hour: 0,
minute: 0,
second: 0,
),
slots: [schedules[i]],
isWeekly: schedules[i].isWeekly,
);
scheduleMap[scheduleKey] = scheduleDay;
} else {
scheduleDay.slots.add(schedules[i]);
}
}
return scheduleMap;
}
Future<Map<String, DayScheduleModel>> _handleResultReturn(
QueryResult<Object?> result,
String mutationKey,
) async {
if (result.hasException) throw Exception(result.exception.toString());
if (result.data == null || result.data!.isEmpty) return {};
final scheduleData = result.data?[mutationKey] as List<dynamic>? ?? [];
return _processScheduleDataList(scheduleData);
}
Future<Map<String, DayScheduleModel>> getStaffSchedule() async {
final response = await _apiClient.query(schema: getStaffScheduleSchema);
if (response.hasException) {
throw Exception(response.exception.toString());
}
final scheduleData = (response.data?['me']
as Map<String, dynamic>?)?['schedule'] as List<dynamic>? ??
[];
return _processScheduleDataList(scheduleData);
}
Stream<Map<String, DayScheduleModel>> getStaffScheduleWithCache() async* {
await for (var response in _apiClient.queryWithCache(
schema: getStaffScheduleSchema,
)) {
if (response == null || response.data == null) continue;
if (response.data == null || response.data!.isEmpty) {
if (response.source?.name == 'cache') continue;
if (response.hasException) {
throw Exception(response.exception.toString());
}
}
final scheduleData = (response.data?['me']
as Map<String, dynamic>?)?['schedule'] as List<dynamic>? ??
[];
yield _processScheduleDataList(scheduleData);
}
}
Future<Map<String, DayScheduleModel>> createStaffSchedule(
List<ScheduleSlotModel> schedules,
) async {
final result = await _apiClient.mutate(
schema: createStaffScheduleMutationSchema,
body: {
'input': [
for (final schedule in schedules) schedule.toJson(),
],
},
);
return _handleResultReturn(result, 'create_staff_schedule');
}
Future<Map<String, DayScheduleModel>> updateStaffSchedule(
List<ScheduleSlotModel> schedules,
) async {
// var result = await _apiClient.mutate(
// schema: updateStaffScheduleMutationSchema,
// body: {
// 'input': [
// for (final schedule in schedules) schedule.toJson(),
// ],
// },
// );
await Future.wait(
[
for (final schedule in schedules)
_apiClient.mutate(
schema: updateStaffScheduleMutationSchema,
body: {
'id': schedule.id,
'input': schedule.toJson(),
},
),
],
);
final result = await _apiClient.mutate(
schema: updateStaffScheduleMutationSchema,
body: {
'id': schedules.last.id,
'input': schedules.last.toJson(),
},
);
return _handleResultReturn(result, 'update_staff_schedule');
}
Future<ScheduleSlotModel?> deleteStaffSchedule(
List<ScheduleSlotModel> schedules,
) async {
final result = await _apiClient.mutate(
schema: deleteStaffScheduleMutationSchema,
body: {
'ids': [
for (final schedule in schedules) schedule.id,
],
},
);
if (result.hasException) throw Exception(result.exception.toString());
if (result.data == null || result.data!.isEmpty) return null;
//TODO: For now backend returns only one value so there is no
// point in returning it
return null;
}
Future<ScheduleSlotModel?> deleteStaffScheduleById(List<String> ids) async {
final result = await _apiClient.mutate(
schema: deleteStaffScheduleMutationSchema,
body: {'ids': ids},
);
if (result.hasException) throw Exception(result.exception.toString());
return null;
}
}

View File

@@ -0,0 +1,116 @@
import 'package:injectable/injectable.dart';
import 'package:krow/features/profile/schedule/data/models/day_schedule_model.dart';
import 'package:krow/features/profile/schedule/data/models/schedule_slot_model.dart';
import 'package:krow/features/profile/schedule/data/staff_schedule_api_provider.dart';
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
import 'package:krow/features/profile/schedule/domain/entities/schedule_slot.dart';
import 'package:krow/features/profile/schedule/domain/staff_schedule_repository.dart';
@Injectable(as: StaffScheduleRepository)
class StaffScheduleRepositoryImpl extends StaffScheduleRepository {
StaffScheduleRepositoryImpl({
required StaffScheduleApiProvider apiProvider,
}) : _apiProvider = apiProvider;
final StaffScheduleApiProvider _apiProvider;
Map<String, DaySchedule> _processSchedulesMap(
Map<String, DayScheduleModel> daySchedulesData,
) {
return daySchedulesData.map(
(key, dayEntry) {
return MapEntry(
key,
DaySchedule(
date: dayEntry.date,
isWeekly: dayEntry.isWeekly,
slots: [
for (final slot in dayEntry.slots)
ScheduleSlot(
id: slot.startAt.microsecondsSinceEpoch.toString(),
startTime: slot.startAt,
endTime: slot.endAt,
remoteId: slot.id,
),
],
),
);
},
);
}
List<ScheduleSlotModel> _expandDailySchedulesToSlots(
List<DaySchedule> schedules,
) {
return [
for (final daySchedule in schedules) ...[
for (final slot in daySchedule.slots)
ScheduleSlotModel(
isWeekly: daySchedule.isWeekly,
startAt: slot.startTime,
endAt: slot.endTime,
id: slot.remoteId,
),
],
];
}
@override
Stream<Map<String, DaySchedule>> getStaffSchedule() async* {
await for (final daySchedulesData
in _apiProvider.getStaffScheduleWithCache()) {
yield _processSchedulesMap(daySchedulesData);
}
}
@override
Future<Map<String, DaySchedule>> createStaffSchedule({
required List<DaySchedule> schedules,
}) async {
return _processSchedulesMap(
await _apiProvider.createStaffSchedule(
_expandDailySchedulesToSlots(schedules),
),
);
}
@override
Future<Map<String, DaySchedule>> updateStaffSchedule({
required List<DaySchedule> schedules,
}) async {
if (schedules.length == 1) {
if (schedules.first.deletedSlots != null) {
await _apiProvider.deleteStaffScheduleById(
[
for (final deletedSlot in schedules.first.deletedSlots!)
deletedSlot.remoteId ?? '',
],
);
}
// In case there were only deletion of slots for the day, and no edits
if (schedules.first.slots.isEmpty) {
return _processSchedulesMap(
await _apiProvider.getStaffSchedule(),
);
}
}
return _processSchedulesMap(
await _apiProvider.updateStaffSchedule(
_expandDailySchedulesToSlots(schedules),
),
);
}
@override
Future<DaySchedule?> deleteStaffSchedule({
required List<DaySchedule> schedules,
}) async {
await _apiProvider.deleteStaffSchedule(
_expandDailySchedulesToSlots(schedules),
);
return null;
}
}