feat: legacy mobile apps created

This commit is contained in:
Achintha Isuru
2025-12-02 23:51:04 -05:00
parent 850441ca64
commit 8e7753b324
1519 changed files with 0 additions and 16 deletions

View File

@@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:krow/core/data/models/skill.dart';
part 'business_skill.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class BusinessSkill {
Skill? skill;
BusinessSkill({this.skill});
factory BusinessSkill.fromJson(Map<String, dynamic> json) {
return _$BusinessSkillFromJson(json);
}
Map<String, dynamic> toJson() => _$BusinessSkillToJson(this);
}

View File

@@ -0,0 +1,33 @@
import 'package:json_annotation/json_annotation.dart';
part 'cancellation_reason.g.dart';
@JsonEnum(fieldRename: FieldRename.snake)
enum CancellationReason {
sickLeave,
vacation,
other,
health,
transportation,
personal,
scheduleConflict,
}
@JsonSerializable(fieldRename: FieldRename.snake)
class CancellationReasonModel {
final String type;
final CancellationReason? reason;
final String? details;
CancellationReasonModel({
required this.type,
required this.reason,
required this.details,
});
factory CancellationReasonModel.fromJson(Map<String, dynamic> json) {
return _$CancellationReasonModelFromJson(json);
}
Map<String, dynamic> toJson() => _$CancellationReasonModelToJson(this);
}

View File

@@ -0,0 +1,64 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:krow/features/shifts/data/models/event_tag.dart';
part 'event.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class Event {
String? id;
Business? business;
String? name;
String? date;
String? additionalInfo;
List<Addon>? addons;
List<EventTag>? tags;
Event({
this.business,
this.name,
this.date,
this.additionalInfo,
this.addons,
this.tags,
});
factory Event.fromJson(Map<String, dynamic> json) {
return _$EventFromJson(json);
}
Map<String, dynamic> toJson() => _$EventToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class Business {
String? name;
String? avatar;
Business({this.name, this.avatar});
factory Business.fromJson(Map<String, dynamic> json) {
return _$BusinessFromJson(json);
}
Map<String, dynamic> toJson() => _$BusinessToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class Addon {
String? name;
Addon({this.name});
factory Addon.fromJson(Map<String, dynamic> json) {
return Addon(
name: json['name'],
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
};
}
}

View File

@@ -0,0 +1,31 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:krow/core/data/models/staff/full_address_model.dart';
import 'package:krow/features/shifts/data/models/event.dart';
import 'package:krow/features/shifts/data/models/shift_contact.dart';
part 'event_shift.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class Shift {
String? id;
String? name;
String? address;
FullAddress? fullAddress;
List<Contact>? contacts;
Event? event;
Shift({
this.id,
this.name,
this.address,
this.event,
this.contacts,
this.fullAddress,
});
factory Shift.fromJson(Map<String, dynamic> json) {
return _$ShiftFromJson(json);
}
Map<String, dynamic> toJson() => _$ShiftToJson(this);
}

View File

@@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'event_tag.g.dart';
@JsonSerializable()
class EventTag{
final String id;
final String name;
final String? slug;
EventTag({required this.name, required this.id, required this.slug});
factory EventTag.fromJson(Map<String, dynamic> json) {
return _$EventTagFromJson(json);
}
Map<String, dynamic> toJson() => _$EventTagToJson(this);
}

View File

@@ -0,0 +1,30 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:krow/features/shifts/data/models/business_skill.dart';
import 'package:krow/features/shifts/data/models/event_shift.dart';
part 'position.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class Position {
String? id;
String? startTime;
String? endTime;
Shift? shift;
BusinessSkill? businessSkill;
double? rate;
@JsonKey(name: 'break')
int? breakMinutes;
Position({
this.id,
this.startTime,
this.endTime,
this.shift,
this.businessSkill,
});
factory Position.fromJson(Map<String, dynamic> json) =>
_$PositionFromJson(json);
Map<String, dynamic> toJson() => _$PositionToJson(this);
}

View File

@@ -0,0 +1,45 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'shift_contact.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class Contact {
final String id;
final String firstName;
final String? avatar;
final String lastName;
final String title;
final AuthInfo authInfo;
Contact({
required this.id,
required this.firstName,
required this.lastName,
required this.title,
required this.avatar,
required this.authInfo,
});
factory Contact.fromJson(Map<String, dynamic> json) {
return _$ContactFromJson(json);
}
Map<String, dynamic> toJson() => _$ContactToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class AuthInfo {
final String email;
final String phone;
AuthInfo({
required this.email,
required this.phone,
});
factory AuthInfo.fromJson(Map<String, dynamic> json) {
return _$AuthInfoFromJson(json);
}
Map<String, dynamic> toJson() => _$AuthInfoToJson(this);
}

View File

@@ -0,0 +1,73 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:krow/features/shifts/data/models/cancellation_reason.dart';
import 'package:krow/features/shifts/data/models/position.dart';
part 'staff_shift.g.dart';
@JsonEnum(fieldRename: FieldRename.snake)
enum EventShiftRoleStaffStatus {
assigned,
confirmed,
ongoing,
completed,
canceledByStaff,
canceledByBusiness,
canceledByAdmin,
requestedReplace,
declineByStaff,
}
@JsonSerializable(fieldRename: FieldRename.snake)
class StaffShift {
String id;
DateTime? statusUpdatedAt;
EventShiftRoleStaffStatus status;
Position? position;
DateTime? startAt;
DateTime? endAt;
DateTime? clockIn;
DateTime? clockOut;
DateTime? breakIn;
DateTime? breakOut;
List<CancellationReasonModel>? cancelReason;
StaffRating? rating;
// Staff? staff;
StaffShift({
required this.id,
required this.status,
this.statusUpdatedAt,
this.position,
this.startAt,
this.endAt,
this.clockIn,
this.clockOut,
this.breakIn,
this.breakOut,
this.rating,
// this.staff
});
factory StaffShift.fromJson(Map<String, dynamic> json) {
return _$StaffShiftFromJson(json);
}
Map<String, dynamic> toJson() => _$StaffShiftToJson(this);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class StaffRating{
final String id;
final double rating;
factory StaffRating.fromJson(Map<String, dynamic> json) {
return _$StaffRatingFromJson(json);
}
StaffRating({required this.id, required this.rating});
Map<String, dynamic> toJson() => _$StaffRatingToJson(this);
}

View File

@@ -0,0 +1,124 @@
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/core/application/clients/api/api_exception.dart';
import 'package:krow/core/data/models/pagination_wrapper/pagination_wrapper.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/data/shifts_gql.dart';
@Injectable()
class ShiftsApiProvider {
final ApiClient _client;
ShiftsApiProvider({required ApiClient client}) : _client = client;
Future<PaginationWrapper> fetchShifts(String status, {String? after}) async {
final QueryResult result = await _client.query(
schema: getShiftsQuery,
body: {'status': status, 'first': 100, 'after': after},
);
if (result.hasException) {
throw Exception(result.exception.toString());
}
return PaginationWrapper.fromJson(
result.data!['staff_shifts'],
(json) => StaffShift.fromJson(json),
);
}
Future<PaginationWrapper> getMissBreakFinishedShift() async {
final QueryResult result = await _client.query(
schema: staffNoBreakShifts,
body: {'first': 100},
);
if (result.hasException) {
throw Exception(result.exception.toString());
}
return PaginationWrapper.fromJson(
result.data!['staff_no_break_shifts'],
(json) => StaffShift.fromJson(json),
);
}
Future<void> confirmShift(String id) async {
final QueryResult result =
await _client.mutate(schema: acceptShiftMutation, body: {'id': id});
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<void> clockInShift(String id) async {
final QueryResult result =
await _client.mutate(schema: trackStaffClockin, body: {'id': id});
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<void> completeShift(String id,
{String? breakIn, String? breakOut, bool isPast = false}) async {
final QueryResult result = await _client.mutate(
schema: isPast ? trackStaffBreak : completeShiftMutation,
body: {'id': id, 'break_in': breakIn, 'break_out': breakOut});
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<void> completeShiftNoBreak(String id,
{String? reason, String? additionalReason}) async {
final QueryResult result = await _client.mutate(
schema: submitNoBreakStaffShiftMutation,
body: {'id': id, 'reason': reason, 'details': additionalReason});
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<void> declineShift(String id,
{String? reason, String? additionalReason}) async {
final QueryResult result = await _client.mutate(
schema: declineStaffShiftMutation,
body: {
'position_id': id,
'reason': reason,
'details': additionalReason
});
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<void> cancelShift(String id,
{String? reason, String? additionalReason}) async {
final QueryResult result = await _client.mutate(
schema: cancelStaffShiftMutation,
body: {
'position_id': id,
'reason': reason,
'details': additionalReason
});
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<StaffShift> getShiftById(String id) async {
final QueryResult result = await _client.query(
schema: getShiftPositionQuery,
body: {'id': id},
);
if (result.hasException) {
throw parseBackendError(result.exception);
}
if (result.data == null || result.data!['staff_shift'] == null) {
throw Exception('No data found');
}
return StaffShift.fromJson(result.data!['staff_shift']);
}
}

View File

@@ -0,0 +1,207 @@
import 'package:krow/core/application/clients/api/gql.dart';
const String _shiftFields = '''
id
status
status_updated_at
start_at
end_at
clock_in
clock_out
break_in
break_out
...position
cancel_reason {
type
reason
details
}
rating {
id
rating
}
''';
const String getShiftsQuery = '''
$_positionFragment
query GetShifts (\$status: EventShiftPositionStaffStatusInput!, \$first: Int!, \$after: String) {
staff_shifts(status: \$status, first: \$first, after: \$after) {
pageInfo {
hasNextPage
}
edges {
node {
$_shiftFields
}
cursor
}
}
}
''';
const String staffNoBreakShifts = '''
$_positionFragment
query staffNoBreakShifts (\$first: Int!) {
staff_no_break_shifts(first: \$first) {
pageInfo{
}
edges {
node {
$_shiftFields
}
}
}
}
''';
const String getShiftPositionQuery = '''
$_positionFragment
query GetShiftPosition (\$id: ID!) {
staff_shift(id: \$id) {
$_shiftFields
}
}
''';
const String acceptShiftMutation = '''
$_positionFragment
mutation AcceptShift(\$id: ID!) {
accept_shift(position_id: \$id) {
$_shiftFields
}
}
''';
const String trackStaffClockin = '''
mutation TrackStaffClockin(\$id: ID!) {
track_staff_clockin(position_staff_id: \$id) {
}
}
''';
const String completeShiftMutation = '''
mutation CompleteShift(\$id: ID!, \$break_in: DateTime, \$break_out: DateTime) {
track_staff_clockout(position_staff_id: \$id, break_in: \$break_in, break_out: \$break_out) {
}
}
''';
const String trackStaffBreak = '''
mutation trackStaffBreak(\$id: ID!, \$break_in: DateTime!, \$break_out: DateTime!) {
track_staff_break(position_staff_id: \$id, break_in: \$break_in, break_out: \$break_out) {
}
}
''';
const String submitNoBreakStaffShiftMutation = '''
mutation SubmitNoBreakStaffShift(\$id: ID!, \$reason: NoBreakShiftPenaltyLogReason, \$details: String) {
submit_no_break_staff_shift(position_staff_id: \$id, reason: \$reason, details: \$details) {
}
}
''';
const String cancelStaffShiftMutation = '''
mutation cancelStaffShiftMutation(\$position_id: ID!, \$reason: CancelShiftPenaltyLogReason, \$details: String) {
cancel_staff_shift(position_staff_id: \$position_id, reason: \$reason, details: \$details) {
}
}
''';
const String declineStaffShiftMutation = '''
mutation DeclineStaffShift(\$position_id: ID!,\$reason: DeclineShiftPenaltyLogReason, \$details: String) {
decline_shift(position_id: \$position_id, reason: \$reason, details: \$details) {
}
}
''';
const _positionFragment = '''
$skillFragment
fragment position on EventShiftPositionStaff {
position {
id
start_time
end_time
rate
break
...shift
...business_skill
}
}
fragment shift on EventShiftPosition {
shift {
id
name
address
...FullAddress
...contacts
event {
id
date
name
...business
additional_info
tags{
id
name
slug
}
addons{
name
}
}
}
}
fragment business_skill on EventShiftPosition {
business_skill {
skill {
...SkillFragment
}
}
}
fragment business on Event {
business {
name
avatar
}
}
fragment contacts on EventShift {
contacts {
id
first_name
last_name
avatar
title
auth_info {
email
phone
}
}
}
fragment FullAddress on EventShift {
full_address {
street_number
zip_code
latitude
longitude
formatted_address
street
region
city
country
}
}
''';

View File

@@ -0,0 +1,155 @@
import 'dart:async';
import 'package:injectable/injectable.dart';
import 'package:intl/intl.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/data/shifts_api_provider.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
import 'package:krow/features/shifts/domain/shifts_repository.dart';
import 'package:krow/features/shifts/presentation/dialogs/complete_dialog/shift_complete_dialog.dart';
@Singleton(as: ShiftsRepository)
class ShiftsRepositoryImpl extends ShiftsRepository {
final ShiftsApiProvider _apiProvider;
StreamController<EventShiftRoleStaffStatus>? _statusController;
ShiftsRepositoryImpl({required ShiftsApiProvider apiProvider})
: _apiProvider = apiProvider;
@override
Stream<EventShiftRoleStaffStatus> get statusStream {
_statusController ??=
StreamController<EventShiftRoleStaffStatus>.broadcast();
return _statusController!.stream;
}
@override
Future<List<ShiftEntity>> getShifts(
{String? lastItemId, required ShiftStatusFilterType statusFilter}) async {
var paginationWrapper = await _apiProvider
.fetchShifts(statusFilterToGqlString(statusFilter), after: lastItemId);
return paginationWrapper.edges.map((e) {
return ShiftEntity.fromStaffShift(
e.node,
cursor: (paginationWrapper.pageInfo?.hasNextPage??false) ? e.cursor : null,
);
}).toList();
}
statusFilterToGqlString(ShiftStatusFilterType statusFilter) {
return statusFilter.name;
}
@override
Future<void> confirmShift(ShiftEntity shiftViewModel) async {
var result = await _apiProvider.confirmShift(shiftViewModel.id);
_statusController?.add(EventShiftRoleStaffStatus.assigned);
_statusController?.add(EventShiftRoleStaffStatus.confirmed);
return result;
}
@override
Future<void> clockInShift(ShiftEntity shiftViewModel) async {
// try {
await _apiProvider.clockInShift(shiftViewModel.id);
_statusController?.add(EventShiftRoleStaffStatus.assigned);
_statusController?.add(EventShiftRoleStaffStatus.ongoing);
// } catch (e) {
// _statusController?.add(EventShiftRoleStaffStatus.assigned);
// _statusController?.add(EventShiftRoleStaffStatus.ongoing);
// rethrow;
// }
}
@override
Future<void> completeShift(
ShiftEntity shiftViewModel, ClockOutDetails clockOutDetails, bool isPast) async {
if (clockOutDetails.reason != null) {
_apiProvider.completeShiftNoBreak(
shiftViewModel.id,
reason: clockOutDetails.reason,
additionalReason: clockOutDetails.additionalReason,
);
} else {
var breakInTime =
DateFormat('H:mm').parse(clockOutDetails.breakStartTime!);
var start = DateTime(
shiftViewModel.startDate.year,
shiftViewModel.startDate.month,
shiftViewModel.startDate.day,
breakInTime.hour,
breakInTime.minute);
var breakOutTime =
DateFormat('H:mm').parse(clockOutDetails.breakEndTime!);
var end = DateTime(
shiftViewModel.startDate.year,
shiftViewModel.startDate.month,
shiftViewModel.startDate.day,
breakOutTime.hour,
breakOutTime.minute);
_apiProvider.completeShift(
shiftViewModel.id,
isPast: isPast,
breakIn: DateFormat('yyyy-MM-dd HH:mm:ss').format(start),
breakOut: DateFormat('yyyy-MM-dd HH:mm:ss').format(end),
);
}
_statusController?.add(EventShiftRoleStaffStatus.ongoing);
_statusController?.add(EventShiftRoleStaffStatus.completed);
}
@override
Future<void> forceClockOut(String id) async {
await _apiProvider.completeShift(id);
_statusController?.add(EventShiftRoleStaffStatus.assigned);
_statusController?.add(EventShiftRoleStaffStatus.confirmed);
}
@override
void dispose() {
_statusController?.close();
_statusController = null;
}
@override
declineShift(
ShiftEntity shiftViewModel, String? reason, String? additionalReason) {
_apiProvider.declineShift(
shiftViewModel.id,
reason: reason,
additionalReason: additionalReason,
);
_statusController?.add(EventShiftRoleStaffStatus.assigned);
_statusController?.add(EventShiftRoleStaffStatus.canceledByStaff);
}
@override
cancelShift(
ShiftEntity shiftViewModel, String? reason, String? additionalReason) {
_apiProvider.cancelShift(
shiftViewModel.id,
reason: reason,
additionalReason: additionalReason,
);
_statusController?.add(EventShiftRoleStaffStatus.assigned);
_statusController?.add(EventShiftRoleStaffStatus.canceledByStaff);
}
@override
Future<ShiftEntity?> getShiftById(String id) async {
return ShiftEntity.fromStaffShift(
cursor: '', await _apiProvider.getShiftById(id));
}
@override
Future<List<ShiftEntity>> getMissBreakFinishedShift() async{
var paginationWrapper = await _apiProvider
.getMissBreakFinishedShift();
return paginationWrapper.edges.map((e) {
return ShiftEntity.fromStaffShift(
e.node,
cursor: (paginationWrapper.pageInfo?.hasNextPage??false) ? e.cursor : null,
);
}).toList();
}
}