Merge pull request #347 from Oloodi/312-feature-integrate-google-maps-places-autocomplete-for-hub-address-validation
Continuation of the development of the mobile apps
This commit is contained in:
9
apps/mobile/NEXT_SPRINT_TASKS.md
Normal file
9
apps/mobile/NEXT_SPRINT_TASKS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## Recommended tasks for the next sprint
|
||||
|
||||
|
||||
* In the mobile applications, since the structure is now finalized (at least for the existing features), we need to **strictly follow best practices while coding**:
|
||||
|
||||
* Break down large widgets into **smaller, reusable widgets**
|
||||
* Add **doc comments** where necessary to improve readability and maintainability
|
||||
* **Remove overly complicated or unnecessary logic** introduced by AI and simplify where possible
|
||||
* **Adhere to the design system** and remove all **hard-coded colors and typography**, using shared tokens instead
|
||||
@@ -5,42 +5,6 @@
|
||||
"storage_bucket": "krow-workforce-dev.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:87d41566f8dda41d7757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.example.krow_workforce"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db",
|
||||
@@ -67,10 +31,10 @@
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
"bundle_id": "com.krowwithus.staff"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -103,10 +67,10 @@
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
"bundle_id": "com.krowwithus.staff"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -139,10 +103,10 @@
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
"bundle_id": "com.krowwithus.staff"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -151,12 +115,20 @@
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:d26bde4ee337b0b17757db",
|
||||
"mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.krowwithus.krow_workforce.dev"
|
||||
"package_name": "com.krowwithus.staff"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-ikdfv3o5f47g36qqgvfq55o4m19n7gk4.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.krowwithus.staff",
|
||||
"certificate_hash": "ac917ae8470ab29f1107c773c6017ff5ea5d102d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
@@ -175,10 +147,10 @@
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
"bundle_id": "com.krowwithus.staff"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
E8C1A28BFABAEE32FB779C9A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 221E00B70DE845BE3D50D0A0 /* GoogleService-Info.plist */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -42,6 +43,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
221E00B70DE845BE3D50D0A0 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
@@ -94,6 +96,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
221E00B70DE845BE3D50D0A0 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -216,6 +219,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
E8C1A28BFABAEE32FB779C9A /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
36
apps/mobile/apps/client/ios/Runner/GoogleService-Info.plist
Normal file
36
apps/mobile/apps/client/ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CLIENT_ID</key>
|
||||
<string>933560802882-jqpv1l3gjmi3m87b2gu1iq4lg46lkdfg.apps.googleusercontent.com</string>
|
||||
<key>REVERSED_CLIENT_ID</key>
|
||||
<string>com.googleusercontent.apps.933560802882-jqpv1l3gjmi3m87b2gu1iq4lg46lkdfg</string>
|
||||
<key>ANDROID_CLIENT_ID</key>
|
||||
<string>933560802882-ikdfv3o5f47g36qqgvfq55o4m19n7gk4.apps.googleusercontent.com</string>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyDyEXkzZAWpXXe4dAesYaZflt5BEtMn9tA</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>933560802882</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.krowwithus.client</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>krow-workforce-dev</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>krow-workforce-dev.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:933560802882:ios:d2b6d743608e2a527757db</string>
|
||||
</dict>
|
||||
</plist>
|
||||
74
apps/mobile/apps/client/lib/firebase_options.dart
Normal file
74
apps/mobile/apps/client/lib/firebase_options.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBqRtZPMGU-Sz5x5UnRrunKu5NSWYyPRn8',
|
||||
appId: '1:933560802882:web:173a841992885bb27757db',
|
||||
messagingSenderId: '933560802882',
|
||||
projectId: 'krow-workforce-dev',
|
||||
authDomain: 'krow-workforce-dev.firebaseapp.com',
|
||||
storageBucket: 'krow-workforce-dev.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4',
|
||||
appId: '1:933560802882:android:da13569105659ead7757db',
|
||||
messagingSenderId: '933560802882',
|
||||
projectId: 'krow-workforce-dev',
|
||||
storageBucket: 'krow-workforce-dev.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDyEXkzZAWpXXe4dAesYaZflt5BEtMn9tA',
|
||||
appId: '1:933560802882:ios:d2b6d743608e2a527757db',
|
||||
messagingSenderId: '933560802882',
|
||||
projectId: 'krow-workforce-dev',
|
||||
storageBucket: 'krow-workforce-dev.firebasestorage.app',
|
||||
iosBundleId: 'com.krowwithus.client',
|
||||
);
|
||||
}
|
||||
@@ -12,10 +12,15 @@ import 'package:client_hubs/client_hubs.dart' as client_hubs;
|
||||
import 'package:client_create_order/client_create_order.dart'
|
||||
as client_create_order;
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'firebase_options.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp();
|
||||
await Firebase.initializeApp(
|
||||
options: kIsWeb ? DefaultFirebaseOptions.currentPlatform : null,
|
||||
);
|
||||
runApp(ModularApp(module: AppModule(), child: const AppWidget()));
|
||||
}
|
||||
|
||||
@@ -54,34 +59,38 @@ class AppWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<core_localization.LocaleBloc>(
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<core_localization.LocaleBloc>(),
|
||||
child:
|
||||
BlocBuilder<
|
||||
core_localization.LocaleBloc,
|
||||
core_localization.LocaleState
|
||||
>(
|
||||
builder:
|
||||
(BuildContext context, core_localization.LocaleState state) {
|
||||
return core_localization.TranslationProvider(
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: "Krow Client",
|
||||
theme: UiTheme.light,
|
||||
routerConfig: Modular.routerConfig,
|
||||
locale: state.locale,
|
||||
supportedLocales: state.supportedLocales,
|
||||
localizationsDelegates:
|
||||
const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
return WebMobileFrame(
|
||||
appName: 'KROW Client\nApplication',
|
||||
logo: Image.asset('assets/logo.png'),
|
||||
child: BlocProvider<core_localization.LocaleBloc>(
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<core_localization.LocaleBloc>(),
|
||||
child:
|
||||
BlocBuilder<
|
||||
core_localization.LocaleBloc,
|
||||
core_localization.LocaleState
|
||||
>(
|
||||
builder:
|
||||
(BuildContext context, core_localization.LocaleState state) {
|
||||
return core_localization.TranslationProvider(
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: "Krow Client",
|
||||
theme: UiTheme.light,
|
||||
routerConfig: Modular.routerConfig,
|
||||
locale: state.locale,
|
||||
supportedLocales: state.supportedLocales,
|
||||
localizationsDelegates:
|
||||
const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ dependencies:
|
||||
path: ../../packages/features/client/hubs
|
||||
client_create_order:
|
||||
path: ../../packages/features/client/create_order
|
||||
krow_core:
|
||||
path: ../../packages/core
|
||||
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_modular: ^6.3.2
|
||||
|
||||
@@ -33,6 +33,29 @@
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
// Import the functions you need from the SDKs you need
|
||||
import { initializeApp } from "https://www.gstatic.com/firebasejs/12.8.0/firebase-app.js";
|
||||
import { getAnalytics } from "https://www.gstatic.com/firebasejs/12.8.0/firebase-analytics.js";
|
||||
// TODO: Add SDKs for Firebase products that you want to use
|
||||
// https://firebase.google.com/docs/web/setup#available-libraries
|
||||
|
||||
// Your web app's Firebase configuration
|
||||
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyBqRtZPMGU-Sz5x5UnRrunKu5NSWYyPRn8",
|
||||
authDomain: "krow-workforce-dev.firebaseapp.com",
|
||||
projectId: "krow-workforce-dev",
|
||||
storageBucket: "krow-workforce-dev.firebasestorage.app",
|
||||
messagingSenderId: "933560802882",
|
||||
appId: "1:933560802882:web:173a841992885bb27757db",
|
||||
measurementId: "G-9S7WEQTDKX"
|
||||
};
|
||||
|
||||
// Initialize Firebase
|
||||
const app = initializeApp(firebaseConfig);
|
||||
const analytics = getAnalytics(app);
|
||||
</script>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,42 +5,6 @@
|
||||
"storage_bucket": "krow-workforce-dev.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:87d41566f8dda41d7757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.example.krow_workforce"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db",
|
||||
@@ -149,42 +113,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:d26bde4ee337b0b17757db",
|
||||
"android_client_info": {
|
||||
"package_name": "com.krowwithus.krow_workforce.dev"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.krow.app.staff.dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
1E967D034ADA3A16EF82CB3E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9F0B07DEC91B141354438F79 /* GoogleService-Info.plist */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
@@ -55,6 +56,7 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9F0B07DEC91B141354438F79 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -94,6 +96,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
9F0B07DEC91B141354438F79 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -216,6 +219,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
1E967D034ADA3A16EF82CB3E /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
36
apps/mobile/apps/staff/ios/Runner/GoogleService-Info.plist
Normal file
36
apps/mobile/apps/staff/ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CLIENT_ID</key>
|
||||
<string>933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com</string>
|
||||
<key>REVERSED_CLIENT_ID</key>
|
||||
<string>com.googleusercontent.apps.933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh</string>
|
||||
<key>ANDROID_CLIENT_ID</key>
|
||||
<string>933560802882-ikdfv3o5f47g36qqgvfq55o4m19n7gk4.apps.googleusercontent.com</string>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyDyEXkzZAWpXXe4dAesYaZflt5BEtMn9tA</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>933560802882</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.krowwithus.staff</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>krow-workforce-dev</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>krow-workforce-dev.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:933560802882:ios:fa584205b356de937757db</string>
|
||||
</dict>
|
||||
</plist>
|
||||
74
apps/mobile/apps/staff/lib/firebase_options.dart
Normal file
74
apps/mobile/apps/staff/lib/firebase_options.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBqRtZPMGU-Sz5x5UnRrunKu5NSWYyPRn8',
|
||||
appId: '1:933560802882:web:4508ef1ee6d4e6907757db',
|
||||
messagingSenderId: '933560802882',
|
||||
projectId: 'krow-workforce-dev',
|
||||
authDomain: 'krow-workforce-dev.firebaseapp.com',
|
||||
storageBucket: 'krow-workforce-dev.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4',
|
||||
appId: '1:933560802882:android:d49b8c0f4d19e95e7757db',
|
||||
messagingSenderId: '933560802882',
|
||||
projectId: 'krow-workforce-dev',
|
||||
storageBucket: 'krow-workforce-dev.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDyEXkzZAWpXXe4dAesYaZflt5BEtMn9tA',
|
||||
appId: '1:933560802882:ios:fa584205b356de937757db',
|
||||
messagingSenderId: '933560802882',
|
||||
projectId: 'krow-workforce-dev',
|
||||
storageBucket: 'krow-workforce-dev.firebasestorage.app',
|
||||
iosBundleId: 'com.krowwithus.staff',
|
||||
);
|
||||
}
|
||||
@@ -5,13 +5,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krowwithus_staff/firebase_options.dart';
|
||||
import 'package:staff_authentication/staff_authentication.dart'
|
||||
as staff_authentication;
|
||||
import 'package:staff_main/staff_main.dart' as staff_main;
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
runApp(ModularApp(module: AppModule(), child: const AppWidget()));
|
||||
}
|
||||
|
||||
@@ -34,33 +38,37 @@ class AppWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<core_localization.LocaleBloc>(
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<core_localization.LocaleBloc>(),
|
||||
child:
|
||||
BlocBuilder<
|
||||
core_localization.LocaleBloc,
|
||||
core_localization.LocaleState
|
||||
>(
|
||||
builder:
|
||||
(BuildContext context, core_localization.LocaleState state) {
|
||||
return core_localization.TranslationProvider(
|
||||
child: MaterialApp.router(
|
||||
title: "KROW Staff",
|
||||
theme: UiTheme.light,
|
||||
routerConfig: Modular.routerConfig,
|
||||
locale: state.locale,
|
||||
supportedLocales: state.supportedLocales,
|
||||
localizationsDelegates:
|
||||
const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
return WebMobileFrame(
|
||||
appName: 'KROW Staff\nApplication',
|
||||
logo: Image.asset('assets/logo.png'),
|
||||
child: BlocProvider<core_localization.LocaleBloc>(
|
||||
create: (BuildContext context) =>
|
||||
Modular.get<core_localization.LocaleBloc>(),
|
||||
child:
|
||||
BlocBuilder<
|
||||
core_localization.LocaleBloc,
|
||||
core_localization.LocaleState
|
||||
>(
|
||||
builder:
|
||||
(BuildContext context, core_localization.LocaleState state) {
|
||||
return core_localization.TranslationProvider(
|
||||
child: MaterialApp.router(
|
||||
title: "KROW Staff",
|
||||
theme: UiTheme.light,
|
||||
routerConfig: Modular.routerConfig,
|
||||
locale: state.locale,
|
||||
supportedLocales: state.supportedLocales,
|
||||
localizationsDelegates:
|
||||
const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,143 @@
|
||||
PODS:
|
||||
- AppCheckCore (11.2.0):
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- Firebase/AppCheck (12.8.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseAppCheck (~> 12.8.0)
|
||||
- Firebase/Auth (12.8.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseAuth (~> 12.8.0)
|
||||
- Firebase/CoreOnly (12.8.0):
|
||||
- FirebaseCore (~> 12.8.0)
|
||||
- firebase_app_check (0.4.1-4):
|
||||
- Firebase/AppCheck (~> 12.8.0)
|
||||
- Firebase/CoreOnly (~> 12.8.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- firebase_auth (6.1.4):
|
||||
- Firebase/Auth (~> 12.8.0)
|
||||
- Firebase/CoreOnly (~> 12.8.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- firebase_core (4.4.0):
|
||||
- Firebase/CoreOnly (~> 12.8.0)
|
||||
- FlutterMacOS
|
||||
- FirebaseAppCheck (12.8.0):
|
||||
- AppCheckCore (~> 11.0)
|
||||
- FirebaseAppCheckInterop (~> 12.8.0)
|
||||
- FirebaseCore (~> 12.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- FirebaseAppCheckInterop (12.8.0)
|
||||
- FirebaseAuth (12.8.0):
|
||||
- FirebaseAppCheckInterop (~> 12.8.0)
|
||||
- FirebaseAuthInterop (~> 12.8.0)
|
||||
- FirebaseCore (~> 12.8.0)
|
||||
- FirebaseCoreExtension (~> 12.8.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GTMSessionFetcher/Core (< 6.0, >= 3.4)
|
||||
- RecaptchaInterop (~> 101.0)
|
||||
- FirebaseAuthInterop (12.8.0)
|
||||
- FirebaseCore (12.8.0):
|
||||
- FirebaseCoreInternal (~> 12.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreExtension (12.8.0):
|
||||
- FirebaseCore (~> 12.8.0)
|
||||
- FirebaseCoreInternal (12.8.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FlutterMacOS (1.0.0)
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Environment (8.1.0):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Network (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (8.1.0)":
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.1.0)
|
||||
- GoogleUtilities/Reachability (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GTMSessionFetcher/Core (5.0.0)
|
||||
- PromisesObjC (2.4.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`)
|
||||
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
|
||||
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AppCheckCore
|
||||
- Firebase
|
||||
- FirebaseAppCheck
|
||||
- FirebaseAppCheckInterop
|
||||
- FirebaseAuth
|
||||
- FirebaseAuthInterop
|
||||
- FirebaseCore
|
||||
- FirebaseCoreExtension
|
||||
- FirebaseCoreInternal
|
||||
- GoogleUtilities
|
||||
- GTMSessionFetcher
|
||||
- PromisesObjC
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
firebase_app_check:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos
|
||||
firebase_auth:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos
|
||||
firebase_core:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
geolocator_apple:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d
|
||||
firebase_app_check: daf97f2d7044e28b68d23bc90e16751acee09732
|
||||
firebase_auth: 2c2438e41f061c03bd67dcb045dfd7bc843b5f52
|
||||
firebase_core: b1697fb64ff2b9ca16baaa821205f8b0c058e5d2
|
||||
FirebaseAppCheck: 11da425929a45c677d537adfff3520ccd57c1690
|
||||
FirebaseAppCheckInterop: ba3dc604a89815379e61ec2365101608d365cf7d
|
||||
FirebaseAuth: 4c289b1a43f5955283244a55cf6bd616de344be5
|
||||
FirebaseAuthInterop: 95363fe96493cb4f106656666a0768b420cba090
|
||||
FirebaseCore: 0dbad74bda10b8fb9ca34ad8f375fb9dd3ebef7c
|
||||
FirebaseCoreExtension: 6605938d51f765d8b18bfcafd2085276a252bee2
|
||||
FirebaseCoreInternal: fe5fa466aeb314787093a7dce9f0beeaad5a2a21
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
GTMSessionFetcher: 02d6e866e90bc236f48a703a041dfe43e6221a29
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
@@ -11,10 +11,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_modular: ^6.3.0
|
||||
|
||||
# Architecture Packages
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
path: ../../packages/design_system
|
||||
core_localization:
|
||||
@@ -27,6 +24,14 @@ dependencies:
|
||||
path: ../../packages/features/staff/availability
|
||||
staff_clock_in:
|
||||
path: ../../packages/features/staff/clock_in
|
||||
staff_main:
|
||||
path: ../../packages/features/staff/staff_main
|
||||
krow_core:
|
||||
path: ../../packages/core
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_modular: ^6.3.0
|
||||
firebase_core: ^4.4.0
|
||||
flutter_bloc: ^8.1.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -34,5 +34,28 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
<script type="module">
|
||||
// Import the functions you need from the SDKs you need
|
||||
import { initializeApp } from "https://www.gstatic.com/firebasejs/12.8.0/firebase-app.js";
|
||||
import { getAnalytics } from "https://www.gstatic.com/firebasejs/12.8.0/firebase-analytics.js";
|
||||
// TODO: Add SDKs for Firebase products that you want to use
|
||||
// https://firebase.google.com/docs/web/setup#available-libraries
|
||||
|
||||
// Your web app's Firebase configuration
|
||||
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyBqRtZPMGU-Sz5x5UnRrunKu5NSWYyPRn8",
|
||||
authDomain: "krow-workforce-dev.firebaseapp.com",
|
||||
projectId: "krow-workforce-dev",
|
||||
storageBucket: "krow-workforce-dev.firebasestorage.app",
|
||||
messagingSenderId: "933560802882",
|
||||
appId: "1:933560802882:web:4508ef1ee6d4e6907757db",
|
||||
measurementId: "G-DTDL7YRRM6"
|
||||
};
|
||||
|
||||
// Initialize Firebase
|
||||
const app = initializeApp(firebaseConfig);
|
||||
const analytics = getAnalytics(app);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,3 +3,4 @@ library core;
|
||||
export 'src/domain/arguments/usecase_argument.dart';
|
||||
export 'src/domain/usecases/usecase.dart';
|
||||
export 'src/utils/date_time_utils.dart';
|
||||
export 'src/presentation/widgets/web_mobile_frame.dart';
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// A wrapper widget that renders the application inside an iPhone-like frame
|
||||
/// specifically for Flutter Web. On other platforms, it simply returns the child.
|
||||
class WebMobileFrame extends StatelessWidget {
|
||||
const WebMobileFrame({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.logo,
|
||||
required this.appName,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Widget logo;
|
||||
final String appName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!kIsWeb) return child;
|
||||
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData.dark(),
|
||||
home: _WebFrameContent(logo: logo, appName: appName, child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WebFrameContent extends StatefulWidget {
|
||||
const _WebFrameContent({
|
||||
required this.child,
|
||||
required this.logo,
|
||||
required this.appName,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Widget logo;
|
||||
final String appName;
|
||||
|
||||
@override
|
||||
State<_WebFrameContent> createState() => _WebFrameContentState();
|
||||
}
|
||||
|
||||
class _WebFrameContentState extends State<_WebFrameContent> {
|
||||
// ignore: unused_field
|
||||
Offset _cursorPosition = Offset.zero;
|
||||
// ignore: unused_field
|
||||
bool _isHovering = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// iPhone 14 Pro Max-ish dimensions (scaled for frame look)
|
||||
const double frameWidth = 390 * 1.2;
|
||||
const double frameHeight = 844 * 1.3;
|
||||
const double borderRadius = 54.0;
|
||||
const double borderThickness = 12.0;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.foreground,
|
||||
body: MouseRegion(
|
||||
cursor: SystemMouseCursors.none,
|
||||
onHover: (PointerHoverEvent event) {
|
||||
setState(() {
|
||||
_cursorPosition = event.position;
|
||||
_isHovering = true;
|
||||
});
|
||||
},
|
||||
onExit: (_) => setState(() => _isHovering = false),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
// Logo and Title on the left (Web only)
|
||||
Positioned(
|
||||
left: 60,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Center(
|
||||
child: Opacity(
|
||||
opacity: 0.5,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
SizedBox(width: 140, child: widget.logo),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
widget.appName,
|
||||
textAlign: TextAlign.left,
|
||||
style: UiTypography.display1b.copyWith(
|
||||
color: UiColors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
height: 2,
|
||||
width: 40,
|
||||
color: UiColors.white.withOpacity(0.3),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Frame and Content
|
||||
Center(
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
// Scale down if screen is too small
|
||||
final double scaleX = constraints.maxWidth / (frameWidth - 150);
|
||||
final double scaleY = constraints.maxHeight / (frameHeight - 220);
|
||||
final double scale = (scaleX < 1 || scaleY < 1)
|
||||
? (scaleX < scaleY ? scaleX : scaleY)
|
||||
: 1.0;
|
||||
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: Container(
|
||||
width: frameWidth,
|
||||
height: frameHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.black,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.6),
|
||||
blurRadius: 40,
|
||||
spreadRadius: 10,
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: const Color(0xFF2C2C2C),
|
||||
width: borderThickness,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
borderRadius - borderThickness,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
// The actual app + status bar
|
||||
Column(
|
||||
children: <Widget>[
|
||||
// Mock iOS Status Bar
|
||||
Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.background,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: UiColors.border,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
// Time side
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
'9:41 PM',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: UiColors.black,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Status Icons side
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.end,
|
||||
spacing: 12,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.signal_cellular_alt,
|
||||
size: 14,
|
||||
color: UiColors.black,
|
||||
),
|
||||
Icon(
|
||||
Icons.wifi,
|
||||
size: 14,
|
||||
color: UiColors.black,
|
||||
),
|
||||
Icon(
|
||||
Icons.battery_full,
|
||||
size: 14,
|
||||
color: UiColors.black,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// The main app content
|
||||
Expanded(child: widget.child),
|
||||
],
|
||||
),
|
||||
|
||||
// Dynamic Island / Notch Mockup
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 35,
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.black,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_isHovering)
|
||||
Positioned(
|
||||
left: _cursorPosition.dx - 15,
|
||||
top: _cursorPosition.dy - 15,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.mutedForeground.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: UiColors.white.withOpacity(0.7), width: 2),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.2),
|
||||
blurRadius: 4,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,5 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
design_system:
|
||||
path: ../design_system
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
/// Generated file. Do not edit.
|
||||
///
|
||||
/// Source: lib/src/l10n
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 1108 (554 per locale)
|
||||
///
|
||||
/// Built on 2026-01-31 at 17:37 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
// dart format off
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:slang/generated.dart';
|
||||
import 'package:slang_flutter/slang_flutter.dart';
|
||||
export 'package:slang_flutter/slang_flutter.dart';
|
||||
|
||||
import 'strings_es.g.dart' deferred as l_es;
|
||||
part 'strings_en.g.dart';
|
||||
|
||||
/// Supported locales.
|
||||
///
|
||||
/// Usage:
|
||||
/// - LocaleSettings.setLocale(AppLocale.en) // set locale
|
||||
/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum
|
||||
/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check
|
||||
enum AppLocale with BaseAppLocale<AppLocale, Translations> {
|
||||
en(languageCode: 'en'),
|
||||
es(languageCode: 'es');
|
||||
|
||||
const AppLocale({
|
||||
required this.languageCode,
|
||||
this.scriptCode, // ignore: unused_element, unused_element_parameter
|
||||
this.countryCode, // ignore: unused_element, unused_element_parameter
|
||||
});
|
||||
|
||||
@override final String languageCode;
|
||||
@override final String? scriptCode;
|
||||
@override final String? countryCode;
|
||||
|
||||
@override
|
||||
Future<Translations> build({
|
||||
Map<String, Node>? overrides,
|
||||
PluralResolver? cardinalResolver,
|
||||
PluralResolver? ordinalResolver,
|
||||
}) async {
|
||||
switch (this) {
|
||||
case AppLocale.en:
|
||||
return TranslationsEn(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
case AppLocale.es:
|
||||
await l_es.loadLibrary();
|
||||
return l_es.TranslationsEs(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Translations buildSync({
|
||||
Map<String, Node>? overrides,
|
||||
PluralResolver? cardinalResolver,
|
||||
PluralResolver? ordinalResolver,
|
||||
}) {
|
||||
switch (this) {
|
||||
case AppLocale.en:
|
||||
return TranslationsEn(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
case AppLocale.es:
|
||||
return l_es.TranslationsEs(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets current instance managed by [LocaleSettings].
|
||||
Translations get translations => LocaleSettings.instance.getTranslations(this);
|
||||
}
|
||||
|
||||
/// Method A: Simple
|
||||
///
|
||||
/// No rebuild after locale change.
|
||||
/// Translation happens during initialization of the widget (call of t).
|
||||
/// Configurable via 'translate_var'.
|
||||
///
|
||||
/// Usage:
|
||||
/// String a = t.someKey.anotherKey;
|
||||
/// String b = t['someKey.anotherKey']; // Only for edge cases!
|
||||
Translations get t => LocaleSettings.instance.currentTranslations;
|
||||
|
||||
/// Method B: Advanced
|
||||
///
|
||||
/// All widgets using this method will trigger a rebuild when locale changes.
|
||||
/// Use this if you have e.g. a settings page where the user can select the locale during runtime.
|
||||
///
|
||||
/// Step 1:
|
||||
/// wrap your App with
|
||||
/// TranslationProvider(
|
||||
/// child: MyApp()
|
||||
/// );
|
||||
///
|
||||
/// Step 2:
|
||||
/// final t = Translations.of(context); // Get t variable.
|
||||
/// String a = t.someKey.anotherKey; // Use t variable.
|
||||
/// String b = t['someKey.anotherKey']; // Only for edge cases!
|
||||
class TranslationProvider extends BaseTranslationProvider<AppLocale, Translations> {
|
||||
TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
|
||||
|
||||
static InheritedLocaleData<AppLocale, Translations> of(BuildContext context) => InheritedLocaleData.of<AppLocale, Translations>(context);
|
||||
}
|
||||
|
||||
/// Method B shorthand via [BuildContext] extension method.
|
||||
/// Configurable via 'translate_var'.
|
||||
///
|
||||
/// Usage (e.g. in a widget's build method):
|
||||
/// context.t.someKey.anotherKey
|
||||
extension BuildContextTranslationsExtension on BuildContext {
|
||||
Translations get t => TranslationProvider.of(this).translations;
|
||||
}
|
||||
|
||||
/// Manages all translation instances and the current locale
|
||||
class LocaleSettings extends BaseFlutterLocaleSettings<AppLocale, Translations> {
|
||||
LocaleSettings._() : super(
|
||||
utils: AppLocaleUtils.instance,
|
||||
lazy: true,
|
||||
);
|
||||
|
||||
static final instance = LocaleSettings._();
|
||||
|
||||
// static aliases (checkout base methods for documentation)
|
||||
static AppLocale get currentLocale => instance.currentLocale;
|
||||
static Stream<AppLocale> getLocaleStream() => instance.getLocaleStream();
|
||||
static Future<AppLocale> setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static Future<AppLocale> setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static Future<AppLocale> useDeviceLocale() => instance.useDeviceLocale();
|
||||
static Future<void> setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver(
|
||||
language: language,
|
||||
locale: locale,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
|
||||
// synchronous versions
|
||||
static AppLocale setLocaleSync(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocaleSync(locale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static AppLocale setLocaleRawSync(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRawSync(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static AppLocale useDeviceLocaleSync() => instance.useDeviceLocaleSync();
|
||||
static void setPluralResolverSync({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolverSync(
|
||||
language: language,
|
||||
locale: locale,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
|
||||
/// Provides utility functions without any side effects.
|
||||
class AppLocaleUtils extends BaseAppLocaleUtils<AppLocale, Translations> {
|
||||
AppLocaleUtils._() : super(
|
||||
baseLocale: AppLocale.en,
|
||||
locales: AppLocale.values,
|
||||
);
|
||||
|
||||
static final instance = AppLocaleUtils._();
|
||||
|
||||
// static aliases (checkout base methods for documentation)
|
||||
static AppLocale parse(String rawLocale) => instance.parse(rawLocale);
|
||||
static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode);
|
||||
static AppLocale findDeviceLocale() => instance.findDeviceLocale();
|
||||
static List<Locale> get supportedLocales => instance.supportedLocales;
|
||||
static List<String> get supportedLocalesRaw => instance.supportedLocalesRaw;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
||||
# Basic Usage
|
||||
|
||||
```dart
|
||||
ExampleConnector.instance.createRecentPayment(createRecentPaymentVariables).execute();
|
||||
ExampleConnector.instance.updateRecentPayment(updateRecentPaymentVariables).execute();
|
||||
ExampleConnector.instance.deleteRecentPayment(deleteRecentPaymentVariables).execute();
|
||||
ExampleConnector.instance.CreateStaff(createStaffVariables).execute();
|
||||
ExampleConnector.instance.UpdateStaff(updateStaffVariables).execute();
|
||||
ExampleConnector.instance.DeleteStaff(deleteStaffVariables).execute();
|
||||
ExampleConnector.instance.getStaffDocumentByKey(getStaffDocumentByKeyVariables).execute();
|
||||
ExampleConnector.instance.listStaffDocumentsByStaffId(listStaffDocumentsByStaffIdVariables).execute();
|
||||
ExampleConnector.instance.listStaffDocumentsByDocumentType(listStaffDocumentsByDocumentTypeVariables).execute();
|
||||
ExampleConnector.instance.listStaffDocumentsByStatus(listStaffDocumentsByStatusVariables).execute();
|
||||
ExampleConnector.instance.listStaffAvailabilities(listStaffAvailabilitiesVariables).execute();
|
||||
ExampleConnector.instance.listStaffAvailabilitiesByStaffId(listStaffAvailabilitiesByStaffIdVariables).execute();
|
||||
ExampleConnector.instance.getStaffAvailabilityByKey(getStaffAvailabilityByKeyVariables).execute();
|
||||
ExampleConnector.instance.listStaffAvailabilitiesByDay(listStaffAvailabilitiesByDayVariables).execute();
|
||||
ExampleConnector.instance.createStaffAvailabilityStats(createStaffAvailabilityStatsVariables).execute();
|
||||
ExampleConnector.instance.updateStaffAvailabilityStats(updateStaffAvailabilityStatsVariables).execute();
|
||||
ExampleConnector.instance.deleteStaffAvailabilityStats(deleteStaffAvailabilityStatsVariables).execute();
|
||||
|
||||
```
|
||||
|
||||
@@ -23,8 +23,8 @@ Optional fields can be discovered based on classes that have `Optional` object t
|
||||
This is an example of a mutation with an optional field:
|
||||
|
||||
```dart
|
||||
await ExampleConnector.instance.updateAttireOption({ ... })
|
||||
.itemId(...)
|
||||
await ExampleConnector.instance.searchInvoiceTemplatesByOwnerAndName({ ... })
|
||||
.offset(...)
|
||||
.execute();
|
||||
```
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,645 @@
|
||||
part of 'generated.dart';
|
||||
|
||||
class GetApplicationByStaffShiftAndRoleVariablesBuilder {
|
||||
String staffId;
|
||||
String shiftId;
|
||||
String roleId;
|
||||
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
|
||||
final FirebaseDataConnect _dataConnect; GetApplicationByStaffShiftAndRoleVariablesBuilder offset(int? t) {
|
||||
_offset.value = t;
|
||||
return this;
|
||||
}
|
||||
GetApplicationByStaffShiftAndRoleVariablesBuilder limit(int? t) {
|
||||
_limit.value = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleVariablesBuilder(this._dataConnect, {required this.staffId,required this.shiftId,required this.roleId,});
|
||||
Deserializer<GetApplicationByStaffShiftAndRoleData> dataDeserializer = (dynamic json) => GetApplicationByStaffShiftAndRoleData.fromJson(jsonDecode(json));
|
||||
Serializer<GetApplicationByStaffShiftAndRoleVariables> varsSerializer = (GetApplicationByStaffShiftAndRoleVariables vars) => jsonEncode(vars.toJson());
|
||||
Future<QueryResult<GetApplicationByStaffShiftAndRoleData, GetApplicationByStaffShiftAndRoleVariables>> execute() {
|
||||
return ref().execute();
|
||||
}
|
||||
|
||||
QueryRef<GetApplicationByStaffShiftAndRoleData, GetApplicationByStaffShiftAndRoleVariables> ref() {
|
||||
GetApplicationByStaffShiftAndRoleVariables vars= GetApplicationByStaffShiftAndRoleVariables(staffId: staffId,shiftId: shiftId,roleId: roleId,offset: _offset,limit: _limit,);
|
||||
return _dataConnect.query("getApplicationByStaffShiftAndRole", dataDeserializer, varsSerializer, vars);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplications {
|
||||
final String id;
|
||||
final String shiftId;
|
||||
final String staffId;
|
||||
final EnumValue<ApplicationStatus> status;
|
||||
final Timestamp? appliedAt;
|
||||
final Timestamp? checkInTime;
|
||||
final Timestamp? checkOutTime;
|
||||
final EnumValue<ApplicationOrigin> origin;
|
||||
final Timestamp? createdAt;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShift shift;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftRole shiftRole;
|
||||
GetApplicationByStaffShiftAndRoleApplications.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
shiftId = nativeFromJson<String>(json['shiftId']),
|
||||
staffId = nativeFromJson<String>(json['staffId']),
|
||||
status = applicationStatusDeserializer(json['status']),
|
||||
appliedAt = json['appliedAt'] == null ? null : Timestamp.fromJson(json['appliedAt']),
|
||||
checkInTime = json['checkInTime'] == null ? null : Timestamp.fromJson(json['checkInTime']),
|
||||
checkOutTime = json['checkOutTime'] == null ? null : Timestamp.fromJson(json['checkOutTime']),
|
||||
origin = applicationOriginDeserializer(json['origin']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
|
||||
shift = GetApplicationByStaffShiftAndRoleApplicationsShift.fromJson(json['shift']),
|
||||
shiftRole = GetApplicationByStaffShiftAndRoleApplicationsShiftRole.fromJson(json['shiftRole']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplications otherTyped = other as GetApplicationByStaffShiftAndRoleApplications;
|
||||
return id == otherTyped.id &&
|
||||
shiftId == otherTyped.shiftId &&
|
||||
staffId == otherTyped.staffId &&
|
||||
status == otherTyped.status &&
|
||||
appliedAt == otherTyped.appliedAt &&
|
||||
checkInTime == otherTyped.checkInTime &&
|
||||
checkOutTime == otherTyped.checkOutTime &&
|
||||
origin == otherTyped.origin &&
|
||||
createdAt == otherTyped.createdAt &&
|
||||
shift == otherTyped.shift &&
|
||||
shiftRole == otherTyped.shiftRole;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, shiftId.hashCode, staffId.hashCode, status.hashCode, appliedAt.hashCode, checkInTime.hashCode, checkOutTime.hashCode, origin.hashCode, createdAt.hashCode, shift.hashCode, shiftRole.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['shiftId'] = nativeToJson<String>(shiftId);
|
||||
json['staffId'] = nativeToJson<String>(staffId);
|
||||
json['status'] =
|
||||
applicationStatusSerializer(status)
|
||||
;
|
||||
if (appliedAt != null) {
|
||||
json['appliedAt'] = appliedAt!.toJson();
|
||||
}
|
||||
if (checkInTime != null) {
|
||||
json['checkInTime'] = checkInTime!.toJson();
|
||||
}
|
||||
if (checkOutTime != null) {
|
||||
json['checkOutTime'] = checkOutTime!.toJson();
|
||||
}
|
||||
json['origin'] =
|
||||
applicationOriginSerializer(origin)
|
||||
;
|
||||
if (createdAt != null) {
|
||||
json['createdAt'] = createdAt!.toJson();
|
||||
}
|
||||
json['shift'] = shift.toJson();
|
||||
json['shiftRole'] = shiftRole.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplications({
|
||||
required this.id,
|
||||
required this.shiftId,
|
||||
required this.staffId,
|
||||
required this.status,
|
||||
this.appliedAt,
|
||||
this.checkInTime,
|
||||
this.checkOutTime,
|
||||
required this.origin,
|
||||
this.createdAt,
|
||||
required this.shift,
|
||||
required this.shiftRole,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShift {
|
||||
final String id;
|
||||
final String title;
|
||||
final Timestamp? date;
|
||||
final Timestamp? startTime;
|
||||
final Timestamp? endTime;
|
||||
final String? location;
|
||||
final EnumValue<ShiftStatus>? status;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrder order;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShift.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
title = nativeFromJson<String>(json['title']),
|
||||
date = json['date'] == null ? null : Timestamp.fromJson(json['date']),
|
||||
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
|
||||
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
|
||||
order = GetApplicationByStaffShiftAndRoleApplicationsShiftOrder.fromJson(json['order']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShift otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShift;
|
||||
return id == otherTyped.id &&
|
||||
title == otherTyped.title &&
|
||||
date == otherTyped.date &&
|
||||
startTime == otherTyped.startTime &&
|
||||
endTime == otherTyped.endTime &&
|
||||
location == otherTyped.location &&
|
||||
status == otherTyped.status &&
|
||||
order == otherTyped.order;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, startTime.hashCode, endTime.hashCode, location.hashCode, status.hashCode, order.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['title'] = nativeToJson<String>(title);
|
||||
if (date != null) {
|
||||
json['date'] = date!.toJson();
|
||||
}
|
||||
if (startTime != null) {
|
||||
json['startTime'] = startTime!.toJson();
|
||||
}
|
||||
if (endTime != null) {
|
||||
json['endTime'] = endTime!.toJson();
|
||||
}
|
||||
if (location != null) {
|
||||
json['location'] = nativeToJson<String?>(location);
|
||||
}
|
||||
if (status != null) {
|
||||
json['status'] =
|
||||
shiftStatusSerializer(status!)
|
||||
;
|
||||
}
|
||||
json['order'] = order.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShift({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.date,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.location,
|
||||
this.status,
|
||||
required this.order,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShiftOrder {
|
||||
final String id;
|
||||
final String? eventName;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub teamHub;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness business;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor? vendor;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrder.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
eventName = json['eventName'] == null ? null : nativeFromJson<String>(json['eventName']),
|
||||
teamHub = GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub.fromJson(json['teamHub']),
|
||||
business = GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness.fromJson(json['business']),
|
||||
vendor = json['vendor'] == null ? null : GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor.fromJson(json['vendor']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrder otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShiftOrder;
|
||||
return id == otherTyped.id &&
|
||||
eventName == otherTyped.eventName &&
|
||||
teamHub == otherTyped.teamHub &&
|
||||
business == otherTyped.business &&
|
||||
vendor == otherTyped.vendor;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, eventName.hashCode, teamHub.hashCode, business.hashCode, vendor.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
if (eventName != null) {
|
||||
json['eventName'] = nativeToJson<String?>(eventName);
|
||||
}
|
||||
json['teamHub'] = teamHub.toJson();
|
||||
json['business'] = business.toJson();
|
||||
if (vendor != null) {
|
||||
json['vendor'] = vendor!.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrder({
|
||||
required this.id,
|
||||
this.eventName,
|
||||
required this.teamHub,
|
||||
required this.business,
|
||||
this.vendor,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub {
|
||||
final String address;
|
||||
final String? placeId;
|
||||
final String hubName;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub.fromJson(dynamic json):
|
||||
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
placeId = json['placeId'] == null ? null : nativeFromJson<String>(json['placeId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub;
|
||||
return address == otherTyped.address &&
|
||||
placeId == otherTyped.placeId &&
|
||||
hubName == otherTyped.hubName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([address.hashCode, placeId.hashCode, hubName.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
if (placeId != null) {
|
||||
json['placeId'] = nativeToJson<String?>(placeId);
|
||||
}
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrderTeamHub({
|
||||
required this.address,
|
||||
this.placeId,
|
||||
required this.hubName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness {
|
||||
final String id;
|
||||
final String businessName;
|
||||
final String? email;
|
||||
final String? contactName;
|
||||
final String? companyLogoUrl;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
businessName = nativeFromJson<String>(json['businessName']),
|
||||
email = json['email'] == null ? null : nativeFromJson<String>(json['email']),
|
||||
contactName = json['contactName'] == null ? null : nativeFromJson<String>(json['contactName']),
|
||||
companyLogoUrl = json['companyLogoUrl'] == null ? null : nativeFromJson<String>(json['companyLogoUrl']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness;
|
||||
return id == otherTyped.id &&
|
||||
businessName == otherTyped.businessName &&
|
||||
email == otherTyped.email &&
|
||||
contactName == otherTyped.contactName &&
|
||||
companyLogoUrl == otherTyped.companyLogoUrl;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, email.hashCode, contactName.hashCode, companyLogoUrl.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['businessName'] = nativeToJson<String>(businessName);
|
||||
if (email != null) {
|
||||
json['email'] = nativeToJson<String?>(email);
|
||||
}
|
||||
if (contactName != null) {
|
||||
json['contactName'] = nativeToJson<String?>(contactName);
|
||||
}
|
||||
if (companyLogoUrl != null) {
|
||||
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrderBusiness({
|
||||
required this.id,
|
||||
required this.businessName,
|
||||
this.email,
|
||||
this.contactName,
|
||||
this.companyLogoUrl,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor {
|
||||
final String id;
|
||||
final String companyName;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
companyName = nativeFromJson<String>(json['companyName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor;
|
||||
return id == otherTyped.id &&
|
||||
companyName == otherTyped.companyName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, companyName.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['companyName'] = nativeToJson<String>(companyName);
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftOrderVendor({
|
||||
required this.id,
|
||||
required this.companyName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShiftRole {
|
||||
final String id;
|
||||
final String roleId;
|
||||
final int count;
|
||||
final int? assigned;
|
||||
final Timestamp? startTime;
|
||||
final Timestamp? endTime;
|
||||
final double? hours;
|
||||
final double? totalValue;
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole role;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftRole.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
roleId = nativeFromJson<String>(json['roleId']),
|
||||
count = nativeFromJson<int>(json['count']),
|
||||
assigned = json['assigned'] == null ? null : nativeFromJson<int>(json['assigned']),
|
||||
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
hours = json['hours'] == null ? null : nativeFromJson<double>(json['hours']),
|
||||
totalValue = json['totalValue'] == null ? null : nativeFromJson<double>(json['totalValue']),
|
||||
role = GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole.fromJson(json['role']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftRole otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShiftRole;
|
||||
return id == otherTyped.id &&
|
||||
roleId == otherTyped.roleId &&
|
||||
count == otherTyped.count &&
|
||||
assigned == otherTyped.assigned &&
|
||||
startTime == otherTyped.startTime &&
|
||||
endTime == otherTyped.endTime &&
|
||||
hours == otherTyped.hours &&
|
||||
totalValue == otherTyped.totalValue &&
|
||||
role == otherTyped.role;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, roleId.hashCode, count.hashCode, assigned.hashCode, startTime.hashCode, endTime.hashCode, hours.hashCode, totalValue.hashCode, role.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['roleId'] = nativeToJson<String>(roleId);
|
||||
json['count'] = nativeToJson<int>(count);
|
||||
if (assigned != null) {
|
||||
json['assigned'] = nativeToJson<int?>(assigned);
|
||||
}
|
||||
if (startTime != null) {
|
||||
json['startTime'] = startTime!.toJson();
|
||||
}
|
||||
if (endTime != null) {
|
||||
json['endTime'] = endTime!.toJson();
|
||||
}
|
||||
if (hours != null) {
|
||||
json['hours'] = nativeToJson<double?>(hours);
|
||||
}
|
||||
if (totalValue != null) {
|
||||
json['totalValue'] = nativeToJson<double?>(totalValue);
|
||||
}
|
||||
json['role'] = role.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftRole({
|
||||
required this.id,
|
||||
required this.roleId,
|
||||
required this.count,
|
||||
this.assigned,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.hours,
|
||||
this.totalValue,
|
||||
required this.role,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole {
|
||||
final String id;
|
||||
final String name;
|
||||
final double costPerHour;
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
name = nativeFromJson<String>(json['name']),
|
||||
costPerHour = nativeFromJson<double>(json['costPerHour']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole otherTyped = other as GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole;
|
||||
return id == otherTyped.id &&
|
||||
name == otherTyped.name &&
|
||||
costPerHour == otherTyped.costPerHour;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, name.hashCode, costPerHour.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['name'] = nativeToJson<String>(name);
|
||||
json['costPerHour'] = nativeToJson<double>(costPerHour);
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleApplicationsShiftRoleRole({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.costPerHour,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleData {
|
||||
final List<GetApplicationByStaffShiftAndRoleApplications> applications;
|
||||
GetApplicationByStaffShiftAndRoleData.fromJson(dynamic json):
|
||||
|
||||
applications = (json['applications'] as List<dynamic>)
|
||||
.map((e) => GetApplicationByStaffShiftAndRoleApplications.fromJson(e))
|
||||
.toList();
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleData otherTyped = other as GetApplicationByStaffShiftAndRoleData;
|
||||
return applications == otherTyped.applications;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => applications.hashCode;
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['applications'] = applications.map((e) => e.toJson()).toList();
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleData({
|
||||
required this.applications,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetApplicationByStaffShiftAndRoleVariables {
|
||||
final String staffId;
|
||||
final String shiftId;
|
||||
final String roleId;
|
||||
late final Optional<int>offset;
|
||||
late final Optional<int>limit;
|
||||
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
||||
GetApplicationByStaffShiftAndRoleVariables.fromJson(Map<String, dynamic> json):
|
||||
|
||||
staffId = nativeFromJson<String>(json['staffId']),
|
||||
shiftId = nativeFromJson<String>(json['shiftId']),
|
||||
roleId = nativeFromJson<String>(json['roleId']) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
|
||||
|
||||
|
||||
limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
|
||||
|
||||
}
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetApplicationByStaffShiftAndRoleVariables otherTyped = other as GetApplicationByStaffShiftAndRoleVariables;
|
||||
return staffId == otherTyped.staffId &&
|
||||
shiftId == otherTyped.shiftId &&
|
||||
roleId == otherTyped.roleId &&
|
||||
offset == otherTyped.offset &&
|
||||
limit == otherTyped.limit;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([staffId.hashCode, shiftId.hashCode, roleId.hashCode, offset.hashCode, limit.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['staffId'] = nativeToJson<String>(staffId);
|
||||
json['shiftId'] = nativeToJson<String>(shiftId);
|
||||
json['roleId'] = nativeToJson<String>(roleId);
|
||||
if(offset.state == OptionalState.set) {
|
||||
json['offset'] = offset.toJson();
|
||||
}
|
||||
if(limit.state == OptionalState.set) {
|
||||
json['limit'] = limit.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
GetApplicationByStaffShiftAndRoleVariables({
|
||||
required this.staffId,
|
||||
required this.shiftId,
|
||||
required this.roleId,
|
||||
required this.offset,
|
||||
required this.limit,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ class GetApplicationsByStaffIdVariablesBuilder {
|
||||
String staffId;
|
||||
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<Timestamp> _dayStart = Optional.optional((json) => json['dayStart'] = Timestamp.fromJson(json['dayStart']), defaultSerializer);
|
||||
Optional<Timestamp> _dayEnd = Optional.optional((json) => json['dayEnd'] = Timestamp.fromJson(json['dayEnd']), defaultSerializer);
|
||||
|
||||
final FirebaseDataConnect _dataConnect; GetApplicationsByStaffIdVariablesBuilder offset(int? t) {
|
||||
_offset.value = t;
|
||||
@@ -13,6 +15,14 @@ class GetApplicationsByStaffIdVariablesBuilder {
|
||||
_limit.value = t;
|
||||
return this;
|
||||
}
|
||||
GetApplicationsByStaffIdVariablesBuilder dayStart(Timestamp? t) {
|
||||
_dayStart.value = t;
|
||||
return this;
|
||||
}
|
||||
GetApplicationsByStaffIdVariablesBuilder dayEnd(Timestamp? t) {
|
||||
_dayEnd.value = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
GetApplicationsByStaffIdVariablesBuilder(this._dataConnect, {required this.staffId,});
|
||||
Deserializer<GetApplicationsByStaffIdData> dataDeserializer = (dynamic json) => GetApplicationsByStaffIdData.fromJson(jsonDecode(json));
|
||||
@@ -22,7 +32,7 @@ class GetApplicationsByStaffIdVariablesBuilder {
|
||||
}
|
||||
|
||||
QueryRef<GetApplicationsByStaffIdData, GetApplicationsByStaffIdVariables> ref() {
|
||||
GetApplicationsByStaffIdVariables vars= GetApplicationsByStaffIdVariables(staffId: staffId,offset: _offset,limit: _limit,);
|
||||
GetApplicationsByStaffIdVariables vars= GetApplicationsByStaffIdVariables(staffId: staffId,offset: _offset,limit: _limit,dayStart: _dayStart,dayEnd: _dayEnd,);
|
||||
return _dataConnect.query("getApplicationsByStaffId", dataDeserializer, varsSerializer, vars);
|
||||
}
|
||||
}
|
||||
@@ -132,6 +142,10 @@ class GetApplicationsByStaffIdApplicationsShift {
|
||||
final Timestamp? endTime;
|
||||
final String? location;
|
||||
final EnumValue<ShiftStatus>? status;
|
||||
final int? durationDays;
|
||||
final String? description;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final GetApplicationsByStaffIdApplicationsShiftOrder order;
|
||||
GetApplicationsByStaffIdApplicationsShift.fromJson(dynamic json):
|
||||
|
||||
@@ -142,6 +156,10 @@ class GetApplicationsByStaffIdApplicationsShift {
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
|
||||
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
|
||||
durationDays = json['durationDays'] == null ? null : nativeFromJson<int>(json['durationDays']),
|
||||
description = json['description'] == null ? null : nativeFromJson<String>(json['description']),
|
||||
latitude = json['latitude'] == null ? null : nativeFromJson<double>(json['latitude']),
|
||||
longitude = json['longitude'] == null ? null : nativeFromJson<double>(json['longitude']),
|
||||
order = GetApplicationsByStaffIdApplicationsShiftOrder.fromJson(json['order']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -160,11 +178,15 @@ class GetApplicationsByStaffIdApplicationsShift {
|
||||
endTime == otherTyped.endTime &&
|
||||
location == otherTyped.location &&
|
||||
status == otherTyped.status &&
|
||||
durationDays == otherTyped.durationDays &&
|
||||
description == otherTyped.description &&
|
||||
latitude == otherTyped.latitude &&
|
||||
longitude == otherTyped.longitude &&
|
||||
order == otherTyped.order;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, startTime.hashCode, endTime.hashCode, location.hashCode, status.hashCode, order.hashCode]);
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, startTime.hashCode, endTime.hashCode, location.hashCode, status.hashCode, durationDays.hashCode, description.hashCode, latitude.hashCode, longitude.hashCode, order.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -188,6 +210,18 @@ class GetApplicationsByStaffIdApplicationsShift {
|
||||
shiftStatusSerializer(status!)
|
||||
;
|
||||
}
|
||||
if (durationDays != null) {
|
||||
json['durationDays'] = nativeToJson<int?>(durationDays);
|
||||
}
|
||||
if (description != null) {
|
||||
json['description'] = nativeToJson<String?>(description);
|
||||
}
|
||||
if (latitude != null) {
|
||||
json['latitude'] = nativeToJson<double?>(latitude);
|
||||
}
|
||||
if (longitude != null) {
|
||||
json['longitude'] = nativeToJson<double?>(longitude);
|
||||
}
|
||||
json['order'] = order.toJson();
|
||||
return json;
|
||||
}
|
||||
@@ -200,6 +234,10 @@ class GetApplicationsByStaffIdApplicationsShift {
|
||||
this.endTime,
|
||||
this.location,
|
||||
this.status,
|
||||
this.durationDays,
|
||||
this.description,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
required this.order,
|
||||
});
|
||||
}
|
||||
@@ -314,12 +352,14 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
|
||||
final String businessName;
|
||||
final String? email;
|
||||
final String? contactName;
|
||||
final String? companyLogoUrl;
|
||||
GetApplicationsByStaffIdApplicationsShiftOrderBusiness.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
businessName = nativeFromJson<String>(json['businessName']),
|
||||
email = json['email'] == null ? null : nativeFromJson<String>(json['email']),
|
||||
contactName = json['contactName'] == null ? null : nativeFromJson<String>(json['contactName']);
|
||||
contactName = json['contactName'] == null ? null : nativeFromJson<String>(json['contactName']),
|
||||
companyLogoUrl = json['companyLogoUrl'] == null ? null : nativeFromJson<String>(json['companyLogoUrl']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
@@ -333,11 +373,12 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
|
||||
return id == otherTyped.id &&
|
||||
businessName == otherTyped.businessName &&
|
||||
email == otherTyped.email &&
|
||||
contactName == otherTyped.contactName;
|
||||
contactName == otherTyped.contactName &&
|
||||
companyLogoUrl == otherTyped.companyLogoUrl;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, email.hashCode, contactName.hashCode]);
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, email.hashCode, contactName.hashCode, companyLogoUrl.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -350,6 +391,9 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
|
||||
if (contactName != null) {
|
||||
json['contactName'] = nativeToJson<String?>(contactName);
|
||||
}
|
||||
if (companyLogoUrl != null) {
|
||||
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -358,6 +402,7 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
|
||||
required this.businessName,
|
||||
this.email,
|
||||
this.contactName,
|
||||
this.companyLogoUrl,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -569,6 +614,8 @@ class GetApplicationsByStaffIdVariables {
|
||||
final String staffId;
|
||||
late final Optional<int>offset;
|
||||
late final Optional<int>limit;
|
||||
late final Optional<Timestamp>dayStart;
|
||||
late final Optional<Timestamp>dayEnd;
|
||||
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
||||
GetApplicationsByStaffIdVariables.fromJson(Map<String, dynamic> json):
|
||||
|
||||
@@ -583,6 +630,14 @@ class GetApplicationsByStaffIdVariables {
|
||||
limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
|
||||
|
||||
|
||||
dayStart = Optional.optional((json) => json['dayStart'] = Timestamp.fromJson(json['dayStart']), defaultSerializer);
|
||||
dayStart.value = json['dayStart'] == null ? null : Timestamp.fromJson(json['dayStart']);
|
||||
|
||||
|
||||
dayEnd = Optional.optional((json) => json['dayEnd'] = Timestamp.fromJson(json['dayEnd']), defaultSerializer);
|
||||
dayEnd.value = json['dayEnd'] == null ? null : Timestamp.fromJson(json['dayEnd']);
|
||||
|
||||
}
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -596,11 +651,13 @@ class GetApplicationsByStaffIdVariables {
|
||||
final GetApplicationsByStaffIdVariables otherTyped = other as GetApplicationsByStaffIdVariables;
|
||||
return staffId == otherTyped.staffId &&
|
||||
offset == otherTyped.offset &&
|
||||
limit == otherTyped.limit;
|
||||
limit == otherTyped.limit &&
|
||||
dayStart == otherTyped.dayStart &&
|
||||
dayEnd == otherTyped.dayEnd;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([staffId.hashCode, offset.hashCode, limit.hashCode]);
|
||||
int get hashCode => Object.hashAll([staffId.hashCode, offset.hashCode, limit.hashCode, dayStart.hashCode, dayEnd.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -612,6 +669,12 @@ class GetApplicationsByStaffIdVariables {
|
||||
if(limit.state == OptionalState.set) {
|
||||
json['limit'] = limit.toJson();
|
||||
}
|
||||
if(dayStart.state == OptionalState.set) {
|
||||
json['dayStart'] = dayStart.toJson();
|
||||
}
|
||||
if(dayEnd.state == OptionalState.set) {
|
||||
json['dayEnd'] = dayEnd.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -619,6 +682,8 @@ class GetApplicationsByStaffIdVariables {
|
||||
required this.staffId,
|
||||
required this.offset,
|
||||
required this.limit,
|
||||
required this.dayStart,
|
||||
required this.dayEnd,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -253,13 +253,15 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
|
||||
final String? notes;
|
||||
final GetShiftRoleByIdShiftRoleShiftOrderBusiness business;
|
||||
final GetShiftRoleByIdShiftRoleShiftOrderVendor? vendor;
|
||||
final GetShiftRoleByIdShiftRoleShiftOrderTeamHub teamHub;
|
||||
GetShiftRoleByIdShiftRoleShiftOrder.fromJson(dynamic json):
|
||||
|
||||
recurringDays = json['recurringDays'] == null ? null : AnyValue.fromJson(json['recurringDays']),
|
||||
permanentDays = json['permanentDays'] == null ? null : AnyValue.fromJson(json['permanentDays']),
|
||||
notes = json['notes'] == null ? null : nativeFromJson<String>(json['notes']),
|
||||
business = GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(json['business']),
|
||||
vendor = json['vendor'] == null ? null : GetShiftRoleByIdShiftRoleShiftOrderVendor.fromJson(json['vendor']);
|
||||
vendor = json['vendor'] == null ? null : GetShiftRoleByIdShiftRoleShiftOrderVendor.fromJson(json['vendor']),
|
||||
teamHub = GetShiftRoleByIdShiftRoleShiftOrderTeamHub.fromJson(json['teamHub']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
@@ -274,11 +276,12 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
|
||||
permanentDays == otherTyped.permanentDays &&
|
||||
notes == otherTyped.notes &&
|
||||
business == otherTyped.business &&
|
||||
vendor == otherTyped.vendor;
|
||||
vendor == otherTyped.vendor &&
|
||||
teamHub == otherTyped.teamHub;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([recurringDays.hashCode, permanentDays.hashCode, notes.hashCode, business.hashCode, vendor.hashCode]);
|
||||
int get hashCode => Object.hashAll([recurringDays.hashCode, permanentDays.hashCode, notes.hashCode, business.hashCode, vendor.hashCode, teamHub.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -296,6 +299,7 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
|
||||
if (vendor != null) {
|
||||
json['vendor'] = vendor!.toJson();
|
||||
}
|
||||
json['teamHub'] = teamHub.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -305,6 +309,7 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
|
||||
this.notes,
|
||||
required this.business,
|
||||
this.vendor,
|
||||
required this.teamHub,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -312,10 +317,12 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
|
||||
class GetShiftRoleByIdShiftRoleShiftOrderBusiness {
|
||||
final String id;
|
||||
final String businessName;
|
||||
final String? companyLogoUrl;
|
||||
GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
businessName = nativeFromJson<String>(json['businessName']);
|
||||
businessName = nativeFromJson<String>(json['businessName']),
|
||||
companyLogoUrl = json['companyLogoUrl'] == null ? null : nativeFromJson<String>(json['companyLogoUrl']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
@@ -327,23 +334,28 @@ class GetShiftRoleByIdShiftRoleShiftOrderBusiness {
|
||||
|
||||
final GetShiftRoleByIdShiftRoleShiftOrderBusiness otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderBusiness;
|
||||
return id == otherTyped.id &&
|
||||
businessName == otherTyped.businessName;
|
||||
businessName == otherTyped.businessName &&
|
||||
companyLogoUrl == otherTyped.companyLogoUrl;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode]);
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, companyLogoUrl.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['businessName'] = nativeToJson<String>(businessName);
|
||||
if (companyLogoUrl != null) {
|
||||
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
GetShiftRoleByIdShiftRoleShiftOrderBusiness({
|
||||
required this.id,
|
||||
required this.businessName,
|
||||
this.companyLogoUrl,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -386,6 +398,40 @@ class GetShiftRoleByIdShiftRoleShiftOrderVendor {
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetShiftRoleByIdShiftRoleShiftOrderTeamHub {
|
||||
final String hubName;
|
||||
GetShiftRoleByIdShiftRoleShiftOrderTeamHub.fromJson(dynamic json):
|
||||
|
||||
hubName = nativeFromJson<String>(json['hubName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GetShiftRoleByIdShiftRoleShiftOrderTeamHub otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderTeamHub;
|
||||
return hubName == otherTyped.hubName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => hubName.hashCode;
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
return json;
|
||||
}
|
||||
|
||||
GetShiftRoleByIdShiftRoleShiftOrderTeamHub({
|
||||
required this.hubName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class GetShiftRoleByIdData {
|
||||
final GetShiftRoleByIdShiftRole? shiftRole;
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
part of 'generated.dart';
|
||||
|
||||
class ListCompletedApplicationsByStaffIdVariablesBuilder {
|
||||
String staffId;
|
||||
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
|
||||
final FirebaseDataConnect _dataConnect; ListCompletedApplicationsByStaffIdVariablesBuilder offset(int? t) {
|
||||
_offset.value = t;
|
||||
return this;
|
||||
}
|
||||
ListCompletedApplicationsByStaffIdVariablesBuilder limit(int? t) {
|
||||
_limit.value = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdVariablesBuilder(this._dataConnect, {required this.staffId,});
|
||||
Deserializer<ListCompletedApplicationsByStaffIdData> dataDeserializer = (dynamic json) => ListCompletedApplicationsByStaffIdData.fromJson(jsonDecode(json));
|
||||
Serializer<ListCompletedApplicationsByStaffIdVariables> varsSerializer = (ListCompletedApplicationsByStaffIdVariables vars) => jsonEncode(vars.toJson());
|
||||
Future<QueryResult<ListCompletedApplicationsByStaffIdData, ListCompletedApplicationsByStaffIdVariables>> execute() {
|
||||
return ref().execute();
|
||||
}
|
||||
|
||||
QueryRef<ListCompletedApplicationsByStaffIdData, ListCompletedApplicationsByStaffIdVariables> ref() {
|
||||
ListCompletedApplicationsByStaffIdVariables vars= ListCompletedApplicationsByStaffIdVariables(staffId: staffId,offset: _offset,limit: _limit,);
|
||||
return _dataConnect.query("listCompletedApplicationsByStaffId", dataDeserializer, varsSerializer, vars);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplications {
|
||||
final String id;
|
||||
final String shiftId;
|
||||
final String staffId;
|
||||
final EnumValue<ApplicationStatus> status;
|
||||
final Timestamp? appliedAt;
|
||||
final Timestamp? checkInTime;
|
||||
final Timestamp? checkOutTime;
|
||||
final EnumValue<ApplicationOrigin> origin;
|
||||
final Timestamp? createdAt;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShift shift;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftRole shiftRole;
|
||||
ListCompletedApplicationsByStaffIdApplications.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
shiftId = nativeFromJson<String>(json['shiftId']),
|
||||
staffId = nativeFromJson<String>(json['staffId']),
|
||||
status = applicationStatusDeserializer(json['status']),
|
||||
appliedAt = json['appliedAt'] == null ? null : Timestamp.fromJson(json['appliedAt']),
|
||||
checkInTime = json['checkInTime'] == null ? null : Timestamp.fromJson(json['checkInTime']),
|
||||
checkOutTime = json['checkOutTime'] == null ? null : Timestamp.fromJson(json['checkOutTime']),
|
||||
origin = applicationOriginDeserializer(json['origin']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
|
||||
shift = ListCompletedApplicationsByStaffIdApplicationsShift.fromJson(json['shift']),
|
||||
shiftRole = ListCompletedApplicationsByStaffIdApplicationsShiftRole.fromJson(json['shiftRole']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplications otherTyped = other as ListCompletedApplicationsByStaffIdApplications;
|
||||
return id == otherTyped.id &&
|
||||
shiftId == otherTyped.shiftId &&
|
||||
staffId == otherTyped.staffId &&
|
||||
status == otherTyped.status &&
|
||||
appliedAt == otherTyped.appliedAt &&
|
||||
checkInTime == otherTyped.checkInTime &&
|
||||
checkOutTime == otherTyped.checkOutTime &&
|
||||
origin == otherTyped.origin &&
|
||||
createdAt == otherTyped.createdAt &&
|
||||
shift == otherTyped.shift &&
|
||||
shiftRole == otherTyped.shiftRole;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, shiftId.hashCode, staffId.hashCode, status.hashCode, appliedAt.hashCode, checkInTime.hashCode, checkOutTime.hashCode, origin.hashCode, createdAt.hashCode, shift.hashCode, shiftRole.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['shiftId'] = nativeToJson<String>(shiftId);
|
||||
json['staffId'] = nativeToJson<String>(staffId);
|
||||
json['status'] =
|
||||
applicationStatusSerializer(status)
|
||||
;
|
||||
if (appliedAt != null) {
|
||||
json['appliedAt'] = appliedAt!.toJson();
|
||||
}
|
||||
if (checkInTime != null) {
|
||||
json['checkInTime'] = checkInTime!.toJson();
|
||||
}
|
||||
if (checkOutTime != null) {
|
||||
json['checkOutTime'] = checkOutTime!.toJson();
|
||||
}
|
||||
json['origin'] =
|
||||
applicationOriginSerializer(origin)
|
||||
;
|
||||
if (createdAt != null) {
|
||||
json['createdAt'] = createdAt!.toJson();
|
||||
}
|
||||
json['shift'] = shift.toJson();
|
||||
json['shiftRole'] = shiftRole.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplications({
|
||||
required this.id,
|
||||
required this.shiftId,
|
||||
required this.staffId,
|
||||
required this.status,
|
||||
this.appliedAt,
|
||||
this.checkInTime,
|
||||
this.checkOutTime,
|
||||
required this.origin,
|
||||
this.createdAt,
|
||||
required this.shift,
|
||||
required this.shiftRole,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShift {
|
||||
final String id;
|
||||
final String title;
|
||||
final Timestamp? date;
|
||||
final Timestamp? startTime;
|
||||
final Timestamp? endTime;
|
||||
final String? location;
|
||||
final EnumValue<ShiftStatus>? status;
|
||||
final String? description;
|
||||
final int? durationDays;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrder order;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShift.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
title = nativeFromJson<String>(json['title']),
|
||||
date = json['date'] == null ? null : Timestamp.fromJson(json['date']),
|
||||
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
|
||||
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
|
||||
description = json['description'] == null ? null : nativeFromJson<String>(json['description']),
|
||||
durationDays = json['durationDays'] == null ? null : nativeFromJson<int>(json['durationDays']),
|
||||
order = ListCompletedApplicationsByStaffIdApplicationsShiftOrder.fromJson(json['order']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShift otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShift;
|
||||
return id == otherTyped.id &&
|
||||
title == otherTyped.title &&
|
||||
date == otherTyped.date &&
|
||||
startTime == otherTyped.startTime &&
|
||||
endTime == otherTyped.endTime &&
|
||||
location == otherTyped.location &&
|
||||
status == otherTyped.status &&
|
||||
description == otherTyped.description &&
|
||||
durationDays == otherTyped.durationDays &&
|
||||
order == otherTyped.order;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, startTime.hashCode, endTime.hashCode, location.hashCode, status.hashCode, description.hashCode, durationDays.hashCode, order.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['title'] = nativeToJson<String>(title);
|
||||
if (date != null) {
|
||||
json['date'] = date!.toJson();
|
||||
}
|
||||
if (startTime != null) {
|
||||
json['startTime'] = startTime!.toJson();
|
||||
}
|
||||
if (endTime != null) {
|
||||
json['endTime'] = endTime!.toJson();
|
||||
}
|
||||
if (location != null) {
|
||||
json['location'] = nativeToJson<String?>(location);
|
||||
}
|
||||
if (status != null) {
|
||||
json['status'] =
|
||||
shiftStatusSerializer(status!)
|
||||
;
|
||||
}
|
||||
if (description != null) {
|
||||
json['description'] = nativeToJson<String?>(description);
|
||||
}
|
||||
if (durationDays != null) {
|
||||
json['durationDays'] = nativeToJson<int?>(durationDays);
|
||||
}
|
||||
json['order'] = order.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShift({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.date,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.location,
|
||||
this.status,
|
||||
this.description,
|
||||
this.durationDays,
|
||||
required this.order,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShiftOrder {
|
||||
final String id;
|
||||
final String? eventName;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub teamHub;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness business;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor? vendor;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrder.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
eventName = json['eventName'] == null ? null : nativeFromJson<String>(json['eventName']),
|
||||
teamHub = ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub.fromJson(json['teamHub']),
|
||||
business = ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness.fromJson(json['business']),
|
||||
vendor = json['vendor'] == null ? null : ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor.fromJson(json['vendor']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrder otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShiftOrder;
|
||||
return id == otherTyped.id &&
|
||||
eventName == otherTyped.eventName &&
|
||||
teamHub == otherTyped.teamHub &&
|
||||
business == otherTyped.business &&
|
||||
vendor == otherTyped.vendor;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, eventName.hashCode, teamHub.hashCode, business.hashCode, vendor.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
if (eventName != null) {
|
||||
json['eventName'] = nativeToJson<String?>(eventName);
|
||||
}
|
||||
json['teamHub'] = teamHub.toJson();
|
||||
json['business'] = business.toJson();
|
||||
if (vendor != null) {
|
||||
json['vendor'] = vendor!.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrder({
|
||||
required this.id,
|
||||
this.eventName,
|
||||
required this.teamHub,
|
||||
required this.business,
|
||||
this.vendor,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub {
|
||||
final String address;
|
||||
final String? placeId;
|
||||
final String hubName;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub.fromJson(dynamic json):
|
||||
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
placeId = json['placeId'] == null ? null : nativeFromJson<String>(json['placeId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub;
|
||||
return address == otherTyped.address &&
|
||||
placeId == otherTyped.placeId &&
|
||||
hubName == otherTyped.hubName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([address.hashCode, placeId.hashCode, hubName.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
if (placeId != null) {
|
||||
json['placeId'] = nativeToJson<String?>(placeId);
|
||||
}
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrderTeamHub({
|
||||
required this.address,
|
||||
this.placeId,
|
||||
required this.hubName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness {
|
||||
final String id;
|
||||
final String businessName;
|
||||
final String? email;
|
||||
final String? contactName;
|
||||
final String? companyLogoUrl;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
businessName = nativeFromJson<String>(json['businessName']),
|
||||
email = json['email'] == null ? null : nativeFromJson<String>(json['email']),
|
||||
contactName = json['contactName'] == null ? null : nativeFromJson<String>(json['contactName']),
|
||||
companyLogoUrl = json['companyLogoUrl'] == null ? null : nativeFromJson<String>(json['companyLogoUrl']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness;
|
||||
return id == otherTyped.id &&
|
||||
businessName == otherTyped.businessName &&
|
||||
email == otherTyped.email &&
|
||||
contactName == otherTyped.contactName &&
|
||||
companyLogoUrl == otherTyped.companyLogoUrl;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, email.hashCode, contactName.hashCode, companyLogoUrl.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['businessName'] = nativeToJson<String>(businessName);
|
||||
if (email != null) {
|
||||
json['email'] = nativeToJson<String?>(email);
|
||||
}
|
||||
if (contactName != null) {
|
||||
json['contactName'] = nativeToJson<String?>(contactName);
|
||||
}
|
||||
if (companyLogoUrl != null) {
|
||||
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrderBusiness({
|
||||
required this.id,
|
||||
required this.businessName,
|
||||
this.email,
|
||||
this.contactName,
|
||||
this.companyLogoUrl,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor {
|
||||
final String id;
|
||||
final String companyName;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
companyName = nativeFromJson<String>(json['companyName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor;
|
||||
return id == otherTyped.id &&
|
||||
companyName == otherTyped.companyName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, companyName.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['companyName'] = nativeToJson<String>(companyName);
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftOrderVendor({
|
||||
required this.id,
|
||||
required this.companyName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShiftRole {
|
||||
final String id;
|
||||
final String roleId;
|
||||
final int count;
|
||||
final int? assigned;
|
||||
final Timestamp? startTime;
|
||||
final Timestamp? endTime;
|
||||
final double? hours;
|
||||
final double? totalValue;
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole role;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftRole.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
roleId = nativeFromJson<String>(json['roleId']),
|
||||
count = nativeFromJson<int>(json['count']),
|
||||
assigned = json['assigned'] == null ? null : nativeFromJson<int>(json['assigned']),
|
||||
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
hours = json['hours'] == null ? null : nativeFromJson<double>(json['hours']),
|
||||
totalValue = json['totalValue'] == null ? null : nativeFromJson<double>(json['totalValue']),
|
||||
role = ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole.fromJson(json['role']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftRole otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShiftRole;
|
||||
return id == otherTyped.id &&
|
||||
roleId == otherTyped.roleId &&
|
||||
count == otherTyped.count &&
|
||||
assigned == otherTyped.assigned &&
|
||||
startTime == otherTyped.startTime &&
|
||||
endTime == otherTyped.endTime &&
|
||||
hours == otherTyped.hours &&
|
||||
totalValue == otherTyped.totalValue &&
|
||||
role == otherTyped.role;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, roleId.hashCode, count.hashCode, assigned.hashCode, startTime.hashCode, endTime.hashCode, hours.hashCode, totalValue.hashCode, role.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['roleId'] = nativeToJson<String>(roleId);
|
||||
json['count'] = nativeToJson<int>(count);
|
||||
if (assigned != null) {
|
||||
json['assigned'] = nativeToJson<int?>(assigned);
|
||||
}
|
||||
if (startTime != null) {
|
||||
json['startTime'] = startTime!.toJson();
|
||||
}
|
||||
if (endTime != null) {
|
||||
json['endTime'] = endTime!.toJson();
|
||||
}
|
||||
if (hours != null) {
|
||||
json['hours'] = nativeToJson<double?>(hours);
|
||||
}
|
||||
if (totalValue != null) {
|
||||
json['totalValue'] = nativeToJson<double?>(totalValue);
|
||||
}
|
||||
json['role'] = role.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftRole({
|
||||
required this.id,
|
||||
required this.roleId,
|
||||
required this.count,
|
||||
this.assigned,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.hours,
|
||||
this.totalValue,
|
||||
required this.role,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole {
|
||||
final String id;
|
||||
final String name;
|
||||
final double costPerHour;
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
name = nativeFromJson<String>(json['name']),
|
||||
costPerHour = nativeFromJson<double>(json['costPerHour']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole otherTyped = other as ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole;
|
||||
return id == otherTyped.id &&
|
||||
name == otherTyped.name &&
|
||||
costPerHour == otherTyped.costPerHour;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, name.hashCode, costPerHour.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['name'] = nativeToJson<String>(name);
|
||||
json['costPerHour'] = nativeToJson<double>(costPerHour);
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdApplicationsShiftRoleRole({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.costPerHour,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdData {
|
||||
final List<ListCompletedApplicationsByStaffIdApplications> applications;
|
||||
ListCompletedApplicationsByStaffIdData.fromJson(dynamic json):
|
||||
|
||||
applications = (json['applications'] as List<dynamic>)
|
||||
.map((e) => ListCompletedApplicationsByStaffIdApplications.fromJson(e))
|
||||
.toList();
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdData otherTyped = other as ListCompletedApplicationsByStaffIdData;
|
||||
return applications == otherTyped.applications;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => applications.hashCode;
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['applications'] = applications.map((e) => e.toJson()).toList();
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdData({
|
||||
required this.applications,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ListCompletedApplicationsByStaffIdVariables {
|
||||
final String staffId;
|
||||
late final Optional<int>offset;
|
||||
late final Optional<int>limit;
|
||||
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
||||
ListCompletedApplicationsByStaffIdVariables.fromJson(Map<String, dynamic> json):
|
||||
|
||||
staffId = nativeFromJson<String>(json['staffId']) {
|
||||
|
||||
|
||||
|
||||
offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
|
||||
|
||||
|
||||
limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
|
||||
|
||||
}
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ListCompletedApplicationsByStaffIdVariables otherTyped = other as ListCompletedApplicationsByStaffIdVariables;
|
||||
return staffId == otherTyped.staffId &&
|
||||
offset == otherTyped.offset &&
|
||||
limit == otherTyped.limit;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([staffId.hashCode, offset.hashCode, limit.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['staffId'] = nativeToJson<String>(staffId);
|
||||
if(offset.state == OptionalState.set) {
|
||||
json['offset'] = offset.toJson();
|
||||
}
|
||||
if(limit.state == OptionalState.set) {
|
||||
json['limit'] = limit.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
ListCompletedApplicationsByStaffIdVariables({
|
||||
required this.staffId,
|
||||
required this.offset,
|
||||
required this.limit,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,6 +204,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
|
||||
final String? locationAddress;
|
||||
final String? description;
|
||||
final String orderId;
|
||||
final EnumValue<ShiftStatus>? status;
|
||||
final int? durationDays;
|
||||
final ListShiftRolesByVendorIdShiftRolesShiftOrder order;
|
||||
ListShiftRolesByVendorIdShiftRolesShift.fromJson(dynamic json):
|
||||
|
||||
@@ -214,6 +216,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
|
||||
locationAddress = json['locationAddress'] == null ? null : nativeFromJson<String>(json['locationAddress']),
|
||||
description = json['description'] == null ? null : nativeFromJson<String>(json['description']),
|
||||
orderId = nativeFromJson<String>(json['orderId']),
|
||||
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
|
||||
durationDays = json['durationDays'] == null ? null : nativeFromJson<int>(json['durationDays']),
|
||||
order = ListShiftRolesByVendorIdShiftRolesShiftOrder.fromJson(json['order']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -232,11 +236,13 @@ class ListShiftRolesByVendorIdShiftRolesShift {
|
||||
locationAddress == otherTyped.locationAddress &&
|
||||
description == otherTyped.description &&
|
||||
orderId == otherTyped.orderId &&
|
||||
status == otherTyped.status &&
|
||||
durationDays == otherTyped.durationDays &&
|
||||
order == otherTyped.order;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, location.hashCode, locationAddress.hashCode, description.hashCode, orderId.hashCode, order.hashCode]);
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, location.hashCode, locationAddress.hashCode, description.hashCode, orderId.hashCode, status.hashCode, durationDays.hashCode, order.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -256,6 +262,14 @@ class ListShiftRolesByVendorIdShiftRolesShift {
|
||||
json['description'] = nativeToJson<String?>(description);
|
||||
}
|
||||
json['orderId'] = nativeToJson<String>(orderId);
|
||||
if (status != null) {
|
||||
json['status'] =
|
||||
shiftStatusSerializer(status!)
|
||||
;
|
||||
}
|
||||
if (durationDays != null) {
|
||||
json['durationDays'] = nativeToJson<int?>(durationDays);
|
||||
}
|
||||
json['order'] = order.toJson();
|
||||
return json;
|
||||
}
|
||||
@@ -268,6 +282,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
|
||||
this.locationAddress,
|
||||
this.description,
|
||||
required this.orderId,
|
||||
this.status,
|
||||
this.durationDays,
|
||||
required this.order,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,675 @@
|
||||
part of 'generated.dart';
|
||||
|
||||
class VaidateDayStaffApplicationVariablesBuilder {
|
||||
String staffId;
|
||||
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<Timestamp> _dayStart = Optional.optional((json) => json['dayStart'] = Timestamp.fromJson(json['dayStart']), defaultSerializer);
|
||||
Optional<Timestamp> _dayEnd = Optional.optional((json) => json['dayEnd'] = Timestamp.fromJson(json['dayEnd']), defaultSerializer);
|
||||
|
||||
final FirebaseDataConnect _dataConnect; VaidateDayStaffApplicationVariablesBuilder offset(int? t) {
|
||||
_offset.value = t;
|
||||
return this;
|
||||
}
|
||||
VaidateDayStaffApplicationVariablesBuilder limit(int? t) {
|
||||
_limit.value = t;
|
||||
return this;
|
||||
}
|
||||
VaidateDayStaffApplicationVariablesBuilder dayStart(Timestamp? t) {
|
||||
_dayStart.value = t;
|
||||
return this;
|
||||
}
|
||||
VaidateDayStaffApplicationVariablesBuilder dayEnd(Timestamp? t) {
|
||||
_dayEnd.value = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationVariablesBuilder(this._dataConnect, {required this.staffId,});
|
||||
Deserializer<VaidateDayStaffApplicationData> dataDeserializer = (dynamic json) => VaidateDayStaffApplicationData.fromJson(jsonDecode(json));
|
||||
Serializer<VaidateDayStaffApplicationVariables> varsSerializer = (VaidateDayStaffApplicationVariables vars) => jsonEncode(vars.toJson());
|
||||
Future<QueryResult<VaidateDayStaffApplicationData, VaidateDayStaffApplicationVariables>> execute() {
|
||||
return ref().execute();
|
||||
}
|
||||
|
||||
QueryRef<VaidateDayStaffApplicationData, VaidateDayStaffApplicationVariables> ref() {
|
||||
VaidateDayStaffApplicationVariables vars= VaidateDayStaffApplicationVariables(staffId: staffId,offset: _offset,limit: _limit,dayStart: _dayStart,dayEnd: _dayEnd,);
|
||||
return _dataConnect.query("vaidateDayStaffApplication", dataDeserializer, varsSerializer, vars);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplications {
|
||||
final String id;
|
||||
final String shiftId;
|
||||
final String staffId;
|
||||
final EnumValue<ApplicationStatus> status;
|
||||
final Timestamp? appliedAt;
|
||||
final Timestamp? checkInTime;
|
||||
final Timestamp? checkOutTime;
|
||||
final EnumValue<ApplicationOrigin> origin;
|
||||
final Timestamp? createdAt;
|
||||
final VaidateDayStaffApplicationApplicationsShift shift;
|
||||
final VaidateDayStaffApplicationApplicationsShiftRole shiftRole;
|
||||
VaidateDayStaffApplicationApplications.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
shiftId = nativeFromJson<String>(json['shiftId']),
|
||||
staffId = nativeFromJson<String>(json['staffId']),
|
||||
status = applicationStatusDeserializer(json['status']),
|
||||
appliedAt = json['appliedAt'] == null ? null : Timestamp.fromJson(json['appliedAt']),
|
||||
checkInTime = json['checkInTime'] == null ? null : Timestamp.fromJson(json['checkInTime']),
|
||||
checkOutTime = json['checkOutTime'] == null ? null : Timestamp.fromJson(json['checkOutTime']),
|
||||
origin = applicationOriginDeserializer(json['origin']),
|
||||
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
|
||||
shift = VaidateDayStaffApplicationApplicationsShift.fromJson(json['shift']),
|
||||
shiftRole = VaidateDayStaffApplicationApplicationsShiftRole.fromJson(json['shiftRole']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplications otherTyped = other as VaidateDayStaffApplicationApplications;
|
||||
return id == otherTyped.id &&
|
||||
shiftId == otherTyped.shiftId &&
|
||||
staffId == otherTyped.staffId &&
|
||||
status == otherTyped.status &&
|
||||
appliedAt == otherTyped.appliedAt &&
|
||||
checkInTime == otherTyped.checkInTime &&
|
||||
checkOutTime == otherTyped.checkOutTime &&
|
||||
origin == otherTyped.origin &&
|
||||
createdAt == otherTyped.createdAt &&
|
||||
shift == otherTyped.shift &&
|
||||
shiftRole == otherTyped.shiftRole;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, shiftId.hashCode, staffId.hashCode, status.hashCode, appliedAt.hashCode, checkInTime.hashCode, checkOutTime.hashCode, origin.hashCode, createdAt.hashCode, shift.hashCode, shiftRole.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['shiftId'] = nativeToJson<String>(shiftId);
|
||||
json['staffId'] = nativeToJson<String>(staffId);
|
||||
json['status'] =
|
||||
applicationStatusSerializer(status)
|
||||
;
|
||||
if (appliedAt != null) {
|
||||
json['appliedAt'] = appliedAt!.toJson();
|
||||
}
|
||||
if (checkInTime != null) {
|
||||
json['checkInTime'] = checkInTime!.toJson();
|
||||
}
|
||||
if (checkOutTime != null) {
|
||||
json['checkOutTime'] = checkOutTime!.toJson();
|
||||
}
|
||||
json['origin'] =
|
||||
applicationOriginSerializer(origin)
|
||||
;
|
||||
if (createdAt != null) {
|
||||
json['createdAt'] = createdAt!.toJson();
|
||||
}
|
||||
json['shift'] = shift.toJson();
|
||||
json['shiftRole'] = shiftRole.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplications({
|
||||
required this.id,
|
||||
required this.shiftId,
|
||||
required this.staffId,
|
||||
required this.status,
|
||||
this.appliedAt,
|
||||
this.checkInTime,
|
||||
this.checkOutTime,
|
||||
required this.origin,
|
||||
this.createdAt,
|
||||
required this.shift,
|
||||
required this.shiftRole,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShift {
|
||||
final String id;
|
||||
final String title;
|
||||
final Timestamp? date;
|
||||
final Timestamp? startTime;
|
||||
final Timestamp? endTime;
|
||||
final String? location;
|
||||
final EnumValue<ShiftStatus>? status;
|
||||
final int? durationDays;
|
||||
final String? description;
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrder order;
|
||||
VaidateDayStaffApplicationApplicationsShift.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
title = nativeFromJson<String>(json['title']),
|
||||
date = json['date'] == null ? null : Timestamp.fromJson(json['date']),
|
||||
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
|
||||
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
|
||||
durationDays = json['durationDays'] == null ? null : nativeFromJson<int>(json['durationDays']),
|
||||
description = json['description'] == null ? null : nativeFromJson<String>(json['description']),
|
||||
order = VaidateDayStaffApplicationApplicationsShiftOrder.fromJson(json['order']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShift otherTyped = other as VaidateDayStaffApplicationApplicationsShift;
|
||||
return id == otherTyped.id &&
|
||||
title == otherTyped.title &&
|
||||
date == otherTyped.date &&
|
||||
startTime == otherTyped.startTime &&
|
||||
endTime == otherTyped.endTime &&
|
||||
location == otherTyped.location &&
|
||||
status == otherTyped.status &&
|
||||
durationDays == otherTyped.durationDays &&
|
||||
description == otherTyped.description &&
|
||||
order == otherTyped.order;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, startTime.hashCode, endTime.hashCode, location.hashCode, status.hashCode, durationDays.hashCode, description.hashCode, order.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['title'] = nativeToJson<String>(title);
|
||||
if (date != null) {
|
||||
json['date'] = date!.toJson();
|
||||
}
|
||||
if (startTime != null) {
|
||||
json['startTime'] = startTime!.toJson();
|
||||
}
|
||||
if (endTime != null) {
|
||||
json['endTime'] = endTime!.toJson();
|
||||
}
|
||||
if (location != null) {
|
||||
json['location'] = nativeToJson<String?>(location);
|
||||
}
|
||||
if (status != null) {
|
||||
json['status'] =
|
||||
shiftStatusSerializer(status!)
|
||||
;
|
||||
}
|
||||
if (durationDays != null) {
|
||||
json['durationDays'] = nativeToJson<int?>(durationDays);
|
||||
}
|
||||
if (description != null) {
|
||||
json['description'] = nativeToJson<String?>(description);
|
||||
}
|
||||
json['order'] = order.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShift({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.date,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.location,
|
||||
this.status,
|
||||
this.durationDays,
|
||||
this.description,
|
||||
required this.order,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShiftOrder {
|
||||
final String id;
|
||||
final String? eventName;
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrderTeamHub teamHub;
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrderBusiness business;
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrderVendor? vendor;
|
||||
VaidateDayStaffApplicationApplicationsShiftOrder.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
eventName = json['eventName'] == null ? null : nativeFromJson<String>(json['eventName']),
|
||||
teamHub = VaidateDayStaffApplicationApplicationsShiftOrderTeamHub.fromJson(json['teamHub']),
|
||||
business = VaidateDayStaffApplicationApplicationsShiftOrderBusiness.fromJson(json['business']),
|
||||
vendor = json['vendor'] == null ? null : VaidateDayStaffApplicationApplicationsShiftOrderVendor.fromJson(json['vendor']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrder otherTyped = other as VaidateDayStaffApplicationApplicationsShiftOrder;
|
||||
return id == otherTyped.id &&
|
||||
eventName == otherTyped.eventName &&
|
||||
teamHub == otherTyped.teamHub &&
|
||||
business == otherTyped.business &&
|
||||
vendor == otherTyped.vendor;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, eventName.hashCode, teamHub.hashCode, business.hashCode, vendor.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
if (eventName != null) {
|
||||
json['eventName'] = nativeToJson<String?>(eventName);
|
||||
}
|
||||
json['teamHub'] = teamHub.toJson();
|
||||
json['business'] = business.toJson();
|
||||
if (vendor != null) {
|
||||
json['vendor'] = vendor!.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShiftOrder({
|
||||
required this.id,
|
||||
this.eventName,
|
||||
required this.teamHub,
|
||||
required this.business,
|
||||
this.vendor,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShiftOrderTeamHub {
|
||||
final String address;
|
||||
final String? placeId;
|
||||
final String hubName;
|
||||
VaidateDayStaffApplicationApplicationsShiftOrderTeamHub.fromJson(dynamic json):
|
||||
|
||||
address = nativeFromJson<String>(json['address']),
|
||||
placeId = json['placeId'] == null ? null : nativeFromJson<String>(json['placeId']),
|
||||
hubName = nativeFromJson<String>(json['hubName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrderTeamHub otherTyped = other as VaidateDayStaffApplicationApplicationsShiftOrderTeamHub;
|
||||
return address == otherTyped.address &&
|
||||
placeId == otherTyped.placeId &&
|
||||
hubName == otherTyped.hubName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([address.hashCode, placeId.hashCode, hubName.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['address'] = nativeToJson<String>(address);
|
||||
if (placeId != null) {
|
||||
json['placeId'] = nativeToJson<String?>(placeId);
|
||||
}
|
||||
json['hubName'] = nativeToJson<String>(hubName);
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShiftOrderTeamHub({
|
||||
required this.address,
|
||||
this.placeId,
|
||||
required this.hubName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShiftOrderBusiness {
|
||||
final String id;
|
||||
final String businessName;
|
||||
final String? email;
|
||||
final String? contactName;
|
||||
final String? companyLogoUrl;
|
||||
VaidateDayStaffApplicationApplicationsShiftOrderBusiness.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
businessName = nativeFromJson<String>(json['businessName']),
|
||||
email = json['email'] == null ? null : nativeFromJson<String>(json['email']),
|
||||
contactName = json['contactName'] == null ? null : nativeFromJson<String>(json['contactName']),
|
||||
companyLogoUrl = json['companyLogoUrl'] == null ? null : nativeFromJson<String>(json['companyLogoUrl']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrderBusiness otherTyped = other as VaidateDayStaffApplicationApplicationsShiftOrderBusiness;
|
||||
return id == otherTyped.id &&
|
||||
businessName == otherTyped.businessName &&
|
||||
email == otherTyped.email &&
|
||||
contactName == otherTyped.contactName &&
|
||||
companyLogoUrl == otherTyped.companyLogoUrl;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, email.hashCode, contactName.hashCode, companyLogoUrl.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['businessName'] = nativeToJson<String>(businessName);
|
||||
if (email != null) {
|
||||
json['email'] = nativeToJson<String?>(email);
|
||||
}
|
||||
if (contactName != null) {
|
||||
json['contactName'] = nativeToJson<String?>(contactName);
|
||||
}
|
||||
if (companyLogoUrl != null) {
|
||||
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShiftOrderBusiness({
|
||||
required this.id,
|
||||
required this.businessName,
|
||||
this.email,
|
||||
this.contactName,
|
||||
this.companyLogoUrl,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShiftOrderVendor {
|
||||
final String id;
|
||||
final String companyName;
|
||||
VaidateDayStaffApplicationApplicationsShiftOrderVendor.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
companyName = nativeFromJson<String>(json['companyName']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShiftOrderVendor otherTyped = other as VaidateDayStaffApplicationApplicationsShiftOrderVendor;
|
||||
return id == otherTyped.id &&
|
||||
companyName == otherTyped.companyName;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, companyName.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['companyName'] = nativeToJson<String>(companyName);
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShiftOrderVendor({
|
||||
required this.id,
|
||||
required this.companyName,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShiftRole {
|
||||
final String id;
|
||||
final String roleId;
|
||||
final int count;
|
||||
final int? assigned;
|
||||
final Timestamp? startTime;
|
||||
final Timestamp? endTime;
|
||||
final double? hours;
|
||||
final double? totalValue;
|
||||
final VaidateDayStaffApplicationApplicationsShiftRoleRole role;
|
||||
VaidateDayStaffApplicationApplicationsShiftRole.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
roleId = nativeFromJson<String>(json['roleId']),
|
||||
count = nativeFromJson<int>(json['count']),
|
||||
assigned = json['assigned'] == null ? null : nativeFromJson<int>(json['assigned']),
|
||||
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
|
||||
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
|
||||
hours = json['hours'] == null ? null : nativeFromJson<double>(json['hours']),
|
||||
totalValue = json['totalValue'] == null ? null : nativeFromJson<double>(json['totalValue']),
|
||||
role = VaidateDayStaffApplicationApplicationsShiftRoleRole.fromJson(json['role']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShiftRole otherTyped = other as VaidateDayStaffApplicationApplicationsShiftRole;
|
||||
return id == otherTyped.id &&
|
||||
roleId == otherTyped.roleId &&
|
||||
count == otherTyped.count &&
|
||||
assigned == otherTyped.assigned &&
|
||||
startTime == otherTyped.startTime &&
|
||||
endTime == otherTyped.endTime &&
|
||||
hours == otherTyped.hours &&
|
||||
totalValue == otherTyped.totalValue &&
|
||||
role == otherTyped.role;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, roleId.hashCode, count.hashCode, assigned.hashCode, startTime.hashCode, endTime.hashCode, hours.hashCode, totalValue.hashCode, role.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['roleId'] = nativeToJson<String>(roleId);
|
||||
json['count'] = nativeToJson<int>(count);
|
||||
if (assigned != null) {
|
||||
json['assigned'] = nativeToJson<int?>(assigned);
|
||||
}
|
||||
if (startTime != null) {
|
||||
json['startTime'] = startTime!.toJson();
|
||||
}
|
||||
if (endTime != null) {
|
||||
json['endTime'] = endTime!.toJson();
|
||||
}
|
||||
if (hours != null) {
|
||||
json['hours'] = nativeToJson<double?>(hours);
|
||||
}
|
||||
if (totalValue != null) {
|
||||
json['totalValue'] = nativeToJson<double?>(totalValue);
|
||||
}
|
||||
json['role'] = role.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShiftRole({
|
||||
required this.id,
|
||||
required this.roleId,
|
||||
required this.count,
|
||||
this.assigned,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
this.hours,
|
||||
this.totalValue,
|
||||
required this.role,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationApplicationsShiftRoleRole {
|
||||
final String id;
|
||||
final String name;
|
||||
final double costPerHour;
|
||||
VaidateDayStaffApplicationApplicationsShiftRoleRole.fromJson(dynamic json):
|
||||
|
||||
id = nativeFromJson<String>(json['id']),
|
||||
name = nativeFromJson<String>(json['name']),
|
||||
costPerHour = nativeFromJson<double>(json['costPerHour']);
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationApplicationsShiftRoleRole otherTyped = other as VaidateDayStaffApplicationApplicationsShiftRoleRole;
|
||||
return id == otherTyped.id &&
|
||||
name == otherTyped.name &&
|
||||
costPerHour == otherTyped.costPerHour;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([id.hashCode, name.hashCode, costPerHour.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['id'] = nativeToJson<String>(id);
|
||||
json['name'] = nativeToJson<String>(name);
|
||||
json['costPerHour'] = nativeToJson<double>(costPerHour);
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationApplicationsShiftRoleRole({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.costPerHour,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationData {
|
||||
final List<VaidateDayStaffApplicationApplications> applications;
|
||||
VaidateDayStaffApplicationData.fromJson(dynamic json):
|
||||
|
||||
applications = (json['applications'] as List<dynamic>)
|
||||
.map((e) => VaidateDayStaffApplicationApplications.fromJson(e))
|
||||
.toList();
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationData otherTyped = other as VaidateDayStaffApplicationData;
|
||||
return applications == otherTyped.applications;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => applications.hashCode;
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['applications'] = applications.map((e) => e.toJson()).toList();
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationData({
|
||||
required this.applications,
|
||||
});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class VaidateDayStaffApplicationVariables {
|
||||
final String staffId;
|
||||
late final Optional<int>offset;
|
||||
late final Optional<int>limit;
|
||||
late final Optional<Timestamp>dayStart;
|
||||
late final Optional<Timestamp>dayEnd;
|
||||
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
||||
VaidateDayStaffApplicationVariables.fromJson(Map<String, dynamic> json):
|
||||
|
||||
staffId = nativeFromJson<String>(json['staffId']) {
|
||||
|
||||
|
||||
|
||||
offset = Optional.optional(nativeFromJson, nativeToJson);
|
||||
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
|
||||
|
||||
|
||||
limit = Optional.optional(nativeFromJson, nativeToJson);
|
||||
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
|
||||
|
||||
|
||||
dayStart = Optional.optional((json) => json['dayStart'] = Timestamp.fromJson(json['dayStart']), defaultSerializer);
|
||||
dayStart.value = json['dayStart'] == null ? null : Timestamp.fromJson(json['dayStart']);
|
||||
|
||||
|
||||
dayEnd = Optional.optional((json) => json['dayEnd'] = Timestamp.fromJson(json['dayEnd']), defaultSerializer);
|
||||
dayEnd.value = json['dayEnd'] == null ? null : Timestamp.fromJson(json['dayEnd']);
|
||||
|
||||
}
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if(identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if(other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VaidateDayStaffApplicationVariables otherTyped = other as VaidateDayStaffApplicationVariables;
|
||||
return staffId == otherTyped.staffId &&
|
||||
offset == otherTyped.offset &&
|
||||
limit == otherTyped.limit &&
|
||||
dayStart == otherTyped.dayStart &&
|
||||
dayEnd == otherTyped.dayEnd;
|
||||
|
||||
}
|
||||
@override
|
||||
int get hashCode => Object.hashAll([staffId.hashCode, offset.hashCode, limit.hashCode, dayStart.hashCode, dayEnd.hashCode]);
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
Map<String, dynamic> json = {};
|
||||
json['staffId'] = nativeToJson<String>(staffId);
|
||||
if(offset.state == OptionalState.set) {
|
||||
json['offset'] = offset.toJson();
|
||||
}
|
||||
if(limit.state == OptionalState.set) {
|
||||
json['limit'] = limit.toJson();
|
||||
}
|
||||
if(dayStart.state == OptionalState.set) {
|
||||
json['dayStart'] = dayStart.toJson();
|
||||
}
|
||||
if(dayEnd.state == OptionalState.set) {
|
||||
json['dayEnd'] = dayEnd.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
VaidateDayStaffApplicationVariables({
|
||||
required this.staffId,
|
||||
required this.offset,
|
||||
required this.limit,
|
||||
required this.dayStart,
|
||||
required this.dayEnd,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
class StaffSession {
|
||||
final domain.User user;
|
||||
final domain.Staff? staff;
|
||||
final String? ownerId;
|
||||
|
||||
const StaffSession({
|
||||
required this.user,
|
||||
this.staff,
|
||||
this.ownerId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,9 @@ class UiIcons {
|
||||
/// Checkmark icon
|
||||
static const IconData check = _IconLib.check;
|
||||
|
||||
/// Checkmark circle icon
|
||||
static const IconData checkCircle = _IconLib.checkCircle;
|
||||
|
||||
/// X/Cancel icon
|
||||
static const IconData close = _IconLib.x;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'ui_colors.dart';
|
||||
import 'ui_typography.dart';
|
||||
import 'ui_constants.dart';
|
||||
import 'ui_typography.dart';
|
||||
|
||||
/// The main entry point for the Staff Design System theme.
|
||||
/// Assembles colors, typography, and constants into a comprehensive Material 3 theme.
|
||||
|
||||
@@ -50,7 +50,7 @@ class UiButton extends StatelessWidget {
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
}) : assert(
|
||||
text != null || child != null,
|
||||
@@ -67,7 +67,7 @@ class UiButton extends StatelessWidget {
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
}) : buttonBuilder = _elevatedButtonBuilder,
|
||||
assert(
|
||||
@@ -85,7 +85,7 @@ class UiButton extends StatelessWidget {
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
}) : buttonBuilder = _outlinedButtonBuilder,
|
||||
assert(
|
||||
@@ -103,7 +103,7 @@ class UiButton extends StatelessWidget {
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
}) : buttonBuilder = _textButtonBuilder,
|
||||
assert(
|
||||
@@ -121,7 +121,7 @@ class UiButton extends StatelessWidget {
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
this.size = UiButtonSize.large,
|
||||
this.fullWidth = false,
|
||||
}) : buttonBuilder = _textButtonBuilder,
|
||||
assert(
|
||||
@@ -132,10 +132,14 @@ class UiButton extends StatelessWidget {
|
||||
@override
|
||||
/// Builds the button UI.
|
||||
Widget build(BuildContext context) {
|
||||
final ButtonStyle? mergedStyle = style != null
|
||||
? _getSizeStyle().merge(style)
|
||||
: _getSizeStyle();
|
||||
|
||||
final Widget button = buttonBuilder(
|
||||
context,
|
||||
onPressed,
|
||||
style,
|
||||
mergedStyle,
|
||||
_buildButtonContent(),
|
||||
);
|
||||
|
||||
@@ -146,6 +150,65 @@ class UiButton extends StatelessWidget {
|
||||
return button;
|
||||
}
|
||||
|
||||
/// Gets the style based on the button size.
|
||||
ButtonStyle _getSizeStyle() {
|
||||
switch (size) {
|
||||
case UiButtonSize.extraSmall:
|
||||
return ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space2,
|
||||
vertical: UiConstants.space1,
|
||||
),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(const Size(0, 28)),
|
||||
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 28)),
|
||||
textStyle: WidgetStateProperty.all(
|
||||
const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
case UiButtonSize.small:
|
||||
return ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space3,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(const Size(0, 36)),
|
||||
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 36)),
|
||||
textStyle: WidgetStateProperty.all(
|
||||
const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
case UiButtonSize.medium:
|
||||
return ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space3,
|
||||
),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(const Size(0, 44)),
|
||||
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 44)),
|
||||
);
|
||||
case UiButtonSize.large:
|
||||
return ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space6,
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(const Size(0, 52)),
|
||||
maximumSize: WidgetStateProperty.all(const Size(double.infinity, 52)),
|
||||
textStyle: WidgetStateProperty.all(
|
||||
const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the button content with optional leading and trailing icons.
|
||||
Widget _buildButtonContent() {
|
||||
if (child != null) {
|
||||
@@ -229,6 +292,9 @@ class UiButton extends StatelessWidget {
|
||||
|
||||
/// Defines the size of a [UiButton].
|
||||
enum UiButtonSize {
|
||||
/// Extra small button (very compact)
|
||||
extraSmall,
|
||||
|
||||
/// Small button (compact)
|
||||
small,
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ export 'src/adapters/clock_in/clock_in_adapter.dart';
|
||||
export 'src/entities/availability/availability_slot.dart';
|
||||
export 'src/entities/availability/day_availability.dart';
|
||||
|
||||
// Coverage
|
||||
export 'src/entities/coverage_domain/coverage_shift.dart';
|
||||
export 'src/entities/coverage_domain/coverage_worker.dart';
|
||||
export 'src/entities/coverage_domain/coverage_stats.dart';
|
||||
|
||||
// Adapters
|
||||
export 'src/adapters/profile/emergency_contact_adapter.dart';
|
||||
export 'src/adapters/profile/experience_adapter.dart';
|
||||
|
||||
@@ -1,10 +1,59 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../entities/shifts/shift.dart';
|
||||
|
||||
/// Adapter for Shift related data.
|
||||
class ShiftAdapter {
|
||||
|
||||
// Note: Conversion logic will likely live in RepoImpl or here if we pass raw objects.
|
||||
// Given we are dealing with generated types that aren't exported by domain,
|
||||
// we might put the logic in Repo or make this accept dynamic/Map if strictly required.
|
||||
// For now, placeholders or simple status helpers.
|
||||
/// Maps application data to a Shift entity.
|
||||
///
|
||||
/// This method handles the common mapping logic used across different
|
||||
/// repositories when converting application data from Data Connect to
|
||||
/// domain Shift entities.
|
||||
static Shift fromApplicationData({
|
||||
required String shiftId,
|
||||
required String roleId,
|
||||
required String roleName,
|
||||
required String businessName,
|
||||
String? companyLogoUrl,
|
||||
required double costPerHour,
|
||||
String? shiftLocation,
|
||||
required String teamHubName,
|
||||
DateTime? shiftDate,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
DateTime? createdAt,
|
||||
required String status,
|
||||
String? description,
|
||||
int? durationDays,
|
||||
required int count,
|
||||
int? assigned,
|
||||
String? eventName,
|
||||
bool hasApplied = false,
|
||||
}) {
|
||||
final String orderName = (eventName ?? '').trim().isNotEmpty
|
||||
? eventName!
|
||||
: businessName;
|
||||
final String title = '$roleName - $orderName';
|
||||
|
||||
return Shift(
|
||||
id: shiftId,
|
||||
roleId: roleId,
|
||||
title: title,
|
||||
clientName: businessName,
|
||||
logoUrl: companyLogoUrl,
|
||||
hourlyRate: costPerHour,
|
||||
location: shiftLocation ?? '',
|
||||
locationAddress: teamHubName,
|
||||
date: shiftDate?.toIso8601String() ?? '',
|
||||
startTime: startTime != null ? DateFormat('HH:mm').format(startTime) : '',
|
||||
endTime: endTime != null ? DateFormat('HH:mm').format(endTime) : '',
|
||||
createdDate: createdAt?.toIso8601String() ?? '',
|
||||
status: status,
|
||||
description: description,
|
||||
durationDays: durationDays,
|
||||
requiredSlots: count,
|
||||
filledSlots: assigned ?? 0,
|
||||
hasApplied: hasApplied,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'coverage_worker.dart';
|
||||
|
||||
/// Domain entity representing a shift in the coverage view.
|
||||
///
|
||||
/// This is a feature-specific domain entity that encapsulates shift information
|
||||
/// including scheduling details and assigned workers.
|
||||
class CoverageShift extends Equatable {
|
||||
/// Creates a [CoverageShift].
|
||||
const CoverageShift({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.location,
|
||||
required this.startTime,
|
||||
required this.workersNeeded,
|
||||
required this.date,
|
||||
required this.workers,
|
||||
});
|
||||
|
||||
/// The unique identifier for the shift.
|
||||
final String id;
|
||||
|
||||
/// The title or role of the shift.
|
||||
final String title;
|
||||
|
||||
/// The location where the shift takes place.
|
||||
final String location;
|
||||
|
||||
/// The start time of the shift (e.g., "16:00").
|
||||
final String startTime;
|
||||
|
||||
/// The number of workers needed for this shift.
|
||||
final int workersNeeded;
|
||||
|
||||
/// The date of the shift.
|
||||
final DateTime date;
|
||||
|
||||
/// The list of workers assigned to this shift.
|
||||
final List<CoverageWorker> workers;
|
||||
|
||||
/// Calculates the coverage percentage for this shift.
|
||||
int get coveragePercent {
|
||||
if (workersNeeded == 0) return 0;
|
||||
return ((workers.length / workersNeeded) * 100).round();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
id,
|
||||
title,
|
||||
location,
|
||||
startTime,
|
||||
workersNeeded,
|
||||
date,
|
||||
workers,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Domain entity representing coverage statistics.
|
||||
///
|
||||
/// Aggregates coverage metrics for a specific date.
|
||||
class CoverageStats extends Equatable {
|
||||
/// Creates a [CoverageStats].
|
||||
const CoverageStats({
|
||||
required this.totalNeeded,
|
||||
required this.totalConfirmed,
|
||||
required this.checkedIn,
|
||||
required this.enRoute,
|
||||
required this.late,
|
||||
});
|
||||
|
||||
/// The total number of workers needed.
|
||||
final int totalNeeded;
|
||||
|
||||
/// The total number of confirmed workers.
|
||||
final int totalConfirmed;
|
||||
|
||||
/// The number of workers who have checked in.
|
||||
final int checkedIn;
|
||||
|
||||
/// The number of workers en route.
|
||||
final int enRoute;
|
||||
|
||||
/// The number of late workers.
|
||||
final int late;
|
||||
|
||||
/// Calculates the overall coverage percentage.
|
||||
int get coveragePercent {
|
||||
if (totalNeeded == 0) return 0;
|
||||
return ((totalConfirmed / totalNeeded) * 100).round();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
totalNeeded,
|
||||
totalConfirmed,
|
||||
checkedIn,
|
||||
enRoute,
|
||||
late,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Worker status enum matching ApplicationStatus from Data Connect.
|
||||
enum CoverageWorkerStatus {
|
||||
/// Application is pending approval.
|
||||
pending,
|
||||
|
||||
/// Application has been accepted.
|
||||
accepted,
|
||||
|
||||
/// Application has been rejected.
|
||||
rejected,
|
||||
|
||||
/// Worker has confirmed attendance.
|
||||
confirmed,
|
||||
|
||||
/// Worker has checked in.
|
||||
checkedIn,
|
||||
|
||||
/// Worker has checked out.
|
||||
checkedOut,
|
||||
|
||||
/// Worker is late.
|
||||
late,
|
||||
|
||||
/// Worker did not show up.
|
||||
noShow,
|
||||
|
||||
/// Shift is completed.
|
||||
completed,
|
||||
}
|
||||
|
||||
/// Domain entity representing a worker in the coverage view.
|
||||
///
|
||||
/// This entity tracks worker status including check-in information.
|
||||
class CoverageWorker extends Equatable {
|
||||
/// Creates a [CoverageWorker].
|
||||
const CoverageWorker({
|
||||
required this.name,
|
||||
required this.status,
|
||||
this.checkInTime,
|
||||
});
|
||||
|
||||
/// The name of the worker.
|
||||
final String name;
|
||||
|
||||
/// The status of the worker.
|
||||
final CoverageWorkerStatus status;
|
||||
|
||||
/// The time the worker checked in, if applicable.
|
||||
final String? checkInTime;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[name, status, checkInTime];
|
||||
}
|
||||
@@ -26,6 +26,9 @@ class Shift extends Equatable {
|
||||
final int? durationDays; // For multi-day shifts
|
||||
final int? requiredSlots;
|
||||
final int? filledSlots;
|
||||
final String? roleId;
|
||||
final bool? hasApplied;
|
||||
final double? totalValue;
|
||||
|
||||
const Shift({
|
||||
required this.id,
|
||||
@@ -53,6 +56,9 @@ class Shift extends Equatable {
|
||||
this.durationDays,
|
||||
this.requiredSlots,
|
||||
this.filledSlots,
|
||||
this.roleId,
|
||||
this.hasApplied,
|
||||
this.totalValue,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -82,6 +88,9 @@ class Shift extends Equatable {
|
||||
durationDays,
|
||||
requiredSlots,
|
||||
filledSlots,
|
||||
roleId,
|
||||
hasApplied,
|
||||
totalValue,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +200,8 @@ class _BillingViewState extends State<BillingView> {
|
||||
const SpendingBreakdownCard(),
|
||||
if (state.invoiceHistory.isEmpty) _buildEmptyState(context)
|
||||
else InvoiceHistorySection(invoices: state.invoiceHistory),
|
||||
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/coverage_repository.dart';
|
||||
import '../../domain/ui_entities/coverage_entities.dart';
|
||||
|
||||
/// Implementation of [CoverageRepository] in the Data layer.
|
||||
///
|
||||
@@ -25,18 +25,14 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
|
||||
final String? businessId =
|
||||
dc.ClientSessionStore.instance.session?.business?.id;
|
||||
print('Coverage: now=${DateTime.now().toIso8601String()}');
|
||||
if (businessId == null || businessId.isEmpty) {
|
||||
print('Coverage: missing businessId for date=${date.toIso8601String()}');
|
||||
return <CoverageShift>[];
|
||||
}
|
||||
|
||||
final DateTime start = DateTime(date.year, date.month, date.day);
|
||||
final DateTime end =
|
||||
DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
|
||||
print(
|
||||
'Coverage: request businessId=$businessId dayStart=${start.toIso8601String()} dayEnd=${end.toIso8601String()}',
|
||||
);
|
||||
|
||||
final fdc.QueryResult<
|
||||
dc.ListShiftRolesByBusinessAndDateRangeData,
|
||||
dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
|
||||
@@ -58,9 +54,6 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
dayEnd: _toTimestamp(end),
|
||||
)
|
||||
.execute();
|
||||
print(
|
||||
'Coverage: ${date.toIso8601String()} staffsApplications=${applicationsResult.data.applications.length}',
|
||||
);
|
||||
|
||||
return _mapCoverageShifts(
|
||||
shiftRolesResult.data.shiftRoles,
|
||||
@@ -84,11 +77,16 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
final List<CoverageWorker> allWorkers =
|
||||
shifts.expand((CoverageShift shift) => shift.workers).toList();
|
||||
final int totalConfirmed = allWorkers.length;
|
||||
final int checkedIn =
|
||||
allWorkers.where((CoverageWorker w) => w.isCheckedIn).length;
|
||||
final int enRoute =
|
||||
allWorkers.where((CoverageWorker w) => w.isEnRoute).length;
|
||||
final int late = allWorkers.where((CoverageWorker w) => w.isLate).length;
|
||||
final int checkedIn = allWorkers
|
||||
.where((CoverageWorker w) => w.status == CoverageWorkerStatus.checkedIn)
|
||||
.length;
|
||||
final int enRoute = allWorkers
|
||||
.where((CoverageWorker w) =>
|
||||
w.status == CoverageWorkerStatus.confirmed && w.checkInTime == null)
|
||||
.length;
|
||||
final int late = allWorkers
|
||||
.where((CoverageWorker w) => w.status == CoverageWorkerStatus.late)
|
||||
.length;
|
||||
|
||||
return CoverageStats(
|
||||
totalNeeded: totalNeeded,
|
||||
@@ -172,24 +170,32 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
.toList();
|
||||
}
|
||||
|
||||
String _mapWorkerStatus(
|
||||
CoverageWorkerStatus _mapWorkerStatus(
|
||||
dc.EnumValue<dc.ApplicationStatus> status,
|
||||
) {
|
||||
if (status is dc.Known<dc.ApplicationStatus>) {
|
||||
switch (status.value) {
|
||||
case dc.ApplicationStatus.LATE:
|
||||
return 'late';
|
||||
case dc.ApplicationStatus.CHECKED_IN:
|
||||
case dc.ApplicationStatus.CHECKED_OUT:
|
||||
case dc.ApplicationStatus.ACCEPTED:
|
||||
case dc.ApplicationStatus.CONFIRMED:
|
||||
case dc.ApplicationStatus.PENDING:
|
||||
return CoverageWorkerStatus.pending;
|
||||
case dc.ApplicationStatus.ACCEPTED:
|
||||
return CoverageWorkerStatus.confirmed;
|
||||
case dc.ApplicationStatus.REJECTED:
|
||||
return CoverageWorkerStatus.rejected;
|
||||
case dc.ApplicationStatus.CONFIRMED:
|
||||
return CoverageWorkerStatus.confirmed;
|
||||
case dc.ApplicationStatus.CHECKED_IN:
|
||||
return CoverageWorkerStatus.checkedIn;
|
||||
case dc.ApplicationStatus.CHECKED_OUT:
|
||||
return CoverageWorkerStatus.checkedOut;
|
||||
case dc.ApplicationStatus.LATE:
|
||||
return CoverageWorkerStatus.late;
|
||||
case dc.ApplicationStatus.NO_SHOW:
|
||||
return 'confirmed';
|
||||
return CoverageWorkerStatus.noShow;
|
||||
case dc.ApplicationStatus.COMPLETED:
|
||||
return CoverageWorkerStatus.completed;
|
||||
}
|
||||
}
|
||||
return 'confirmed';
|
||||
return CoverageWorkerStatus.pending;
|
||||
}
|
||||
|
||||
String? _formatTime(fdc.Timestamp? timestamp) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../ui_entities/coverage_entities.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Repository interface for coverage-related operations.
|
||||
///
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Domain entity representing a shift in the coverage view.
|
||||
///
|
||||
/// This is a feature-specific domain entity that encapsulates shift information
|
||||
/// including scheduling details and assigned workers.
|
||||
class CoverageShift extends Equatable {
|
||||
/// Creates a [CoverageShift].
|
||||
const CoverageShift({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.location,
|
||||
required this.startTime,
|
||||
required this.workersNeeded,
|
||||
required this.date,
|
||||
required this.workers,
|
||||
});
|
||||
|
||||
/// The unique identifier for the shift.
|
||||
final String id;
|
||||
|
||||
/// The title or role of the shift.
|
||||
final String title;
|
||||
|
||||
/// The location where the shift takes place.
|
||||
final String location;
|
||||
|
||||
/// The start time of the shift (e.g., "16:00").
|
||||
final String startTime;
|
||||
|
||||
/// The number of workers needed for this shift.
|
||||
final int workersNeeded;
|
||||
|
||||
/// The date of the shift.
|
||||
final DateTime date;
|
||||
|
||||
/// The list of workers assigned to this shift.
|
||||
final List<CoverageWorker> workers;
|
||||
|
||||
/// Calculates the coverage percentage for this shift.
|
||||
int get coveragePercent {
|
||||
if (workersNeeded == 0) return 0;
|
||||
return ((workers.length / workersNeeded) * 100).round();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
id,
|
||||
title,
|
||||
location,
|
||||
startTime,
|
||||
workersNeeded,
|
||||
date,
|
||||
workers,
|
||||
];
|
||||
}
|
||||
|
||||
/// Domain entity representing a worker in the coverage view.
|
||||
///
|
||||
/// This entity tracks worker status including check-in information.
|
||||
class CoverageWorker extends Equatable {
|
||||
/// Creates a [CoverageWorker].
|
||||
const CoverageWorker({
|
||||
required this.name,
|
||||
required this.status,
|
||||
this.checkInTime,
|
||||
});
|
||||
|
||||
/// The name of the worker.
|
||||
final String name;
|
||||
|
||||
/// The status of the worker ('confirmed', 'late', etc.).
|
||||
final String status;
|
||||
|
||||
/// The time the worker checked in, if applicable.
|
||||
final String? checkInTime;
|
||||
|
||||
/// Returns true if the worker is checked in.
|
||||
bool get isCheckedIn => status == 'confirmed' && checkInTime != null;
|
||||
|
||||
/// Returns true if the worker is en route.
|
||||
bool get isEnRoute => status == 'confirmed' && checkInTime == null;
|
||||
|
||||
/// Returns true if the worker is late.
|
||||
bool get isLate => status == 'late';
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[name, status, checkInTime];
|
||||
}
|
||||
|
||||
/// Domain entity representing coverage statistics.
|
||||
///
|
||||
/// Aggregates coverage metrics for a specific date.
|
||||
class CoverageStats extends Equatable {
|
||||
/// Creates a [CoverageStats].
|
||||
const CoverageStats({
|
||||
required this.totalNeeded,
|
||||
required this.totalConfirmed,
|
||||
required this.checkedIn,
|
||||
required this.enRoute,
|
||||
required this.late,
|
||||
});
|
||||
|
||||
/// The total number of workers needed.
|
||||
final int totalNeeded;
|
||||
|
||||
/// The total number of confirmed workers.
|
||||
final int totalConfirmed;
|
||||
|
||||
/// The number of workers who have checked in.
|
||||
final int checkedIn;
|
||||
|
||||
/// The number of workers en route.
|
||||
final int enRoute;
|
||||
|
||||
/// The number of late workers.
|
||||
final int late;
|
||||
|
||||
/// Calculates the overall coverage percentage.
|
||||
int get coveragePercent {
|
||||
if (totalNeeded == 0) return 0;
|
||||
return ((totalConfirmed / totalNeeded) * 100).round();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
totalNeeded,
|
||||
totalConfirmed,
|
||||
checkedIn,
|
||||
enRoute,
|
||||
late,
|
||||
];
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../arguments/get_coverage_stats_arguments.dart';
|
||||
import '../repositories/coverage_repository.dart';
|
||||
import '../ui_entities/coverage_entities.dart';
|
||||
|
||||
/// Use case for fetching coverage statistics for a specific date.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../arguments/get_shifts_for_date_arguments.dart';
|
||||
import '../repositories/coverage_repository.dart';
|
||||
import '../ui_entities/coverage_entities.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Use case for fetching shifts for a specific date.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/arguments/get_coverage_stats_arguments.dart';
|
||||
import '../../domain/arguments/get_shifts_for_date_arguments.dart';
|
||||
import '../../domain/ui_entities/coverage_entities.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_coverage_stats_usecase.dart';
|
||||
import '../../domain/usecases/get_shifts_for_date_usecase.dart';
|
||||
import 'coverage_event.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../domain/ui_entities/coverage_entities.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Enum representing the status of coverage data loading.
|
||||
enum CoverageStatus {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../domain/ui_entities/coverage_entities.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Quick statistics cards showing coverage metrics.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../domain/ui_entities/coverage_entities.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// List of shifts with their workers.
|
||||
///
|
||||
@@ -194,7 +194,8 @@ class _ShiftHeader extends StatelessWidget {
|
||||
size: UiConstants.space3,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
Expanded(child: Text(
|
||||
Expanded(
|
||||
child: Text(
|
||||
location,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -314,36 +315,92 @@ class _WorkerRow extends StatelessWidget {
|
||||
Color badgeText;
|
||||
String badgeLabel;
|
||||
|
||||
if (worker.isCheckedIn) {
|
||||
bg = UiColors.textSuccess.withOpacity(0.1);
|
||||
border = UiColors.textSuccess;
|
||||
textBg = UiColors.textSuccess.withOpacity(0.2);
|
||||
textColor = UiColors.textSuccess;
|
||||
icon = UiIcons.success;
|
||||
statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}';
|
||||
badgeBg = UiColors.textSuccess;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeLabel = 'On Site';
|
||||
} else if (worker.isEnRoute) {
|
||||
bg = UiColors.textWarning.withOpacity(0.1);
|
||||
border = UiColors.textWarning;
|
||||
textBg = UiColors.textWarning.withOpacity(0.2);
|
||||
textColor = UiColors.textWarning;
|
||||
icon = UiIcons.clock;
|
||||
statusText = 'En Route - Expected $shiftStartTime';
|
||||
badgeBg = UiColors.textWarning;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeLabel = 'En Route';
|
||||
} else {
|
||||
bg = UiColors.destructive.withOpacity(0.1);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withOpacity(0.2);
|
||||
textColor = UiColors.destructive;
|
||||
icon = UiIcons.warning;
|
||||
statusText = '⚠ Running Late';
|
||||
badgeBg = UiColors.destructive;
|
||||
badgeText = UiColors.destructiveForeground;
|
||||
badgeLabel = 'Late';
|
||||
switch (worker.status) {
|
||||
case CoverageWorkerStatus.checkedIn:
|
||||
bg = UiColors.textSuccess.withOpacity(0.1);
|
||||
border = UiColors.textSuccess;
|
||||
textBg = UiColors.textSuccess.withOpacity(0.2);
|
||||
textColor = UiColors.textSuccess;
|
||||
icon = UiIcons.success;
|
||||
statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}';
|
||||
badgeBg = UiColors.textSuccess;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeLabel = 'On Site';
|
||||
case CoverageWorkerStatus.confirmed:
|
||||
if (worker.checkInTime == null) {
|
||||
bg = UiColors.textWarning.withOpacity(0.1);
|
||||
border = UiColors.textWarning;
|
||||
textBg = UiColors.textWarning.withOpacity(0.2);
|
||||
textColor = UiColors.textWarning;
|
||||
icon = UiIcons.clock;
|
||||
statusText = 'En Route - Expected $shiftStartTime';
|
||||
badgeBg = UiColors.textWarning;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeLabel = 'En Route';
|
||||
} else {
|
||||
bg = UiColors.muted.withOpacity(0.1);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withOpacity(0.2);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.success;
|
||||
statusText = 'Confirmed';
|
||||
badgeBg = UiColors.muted;
|
||||
badgeText = UiColors.textPrimary;
|
||||
badgeLabel = 'Confirmed';
|
||||
}
|
||||
case CoverageWorkerStatus.late:
|
||||
bg = UiColors.destructive.withOpacity(0.1);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withOpacity(0.2);
|
||||
textColor = UiColors.destructive;
|
||||
icon = UiIcons.warning;
|
||||
statusText = '⚠ Running Late';
|
||||
badgeBg = UiColors.destructive;
|
||||
badgeText = UiColors.destructiveForeground;
|
||||
badgeLabel = 'Late';
|
||||
case CoverageWorkerStatus.checkedOut:
|
||||
bg = UiColors.muted.withOpacity(0.1);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withOpacity(0.2);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.success;
|
||||
statusText = 'Checked Out';
|
||||
badgeBg = UiColors.muted;
|
||||
badgeText = UiColors.textPrimary;
|
||||
badgeLabel = 'Done';
|
||||
case CoverageWorkerStatus.noShow:
|
||||
bg = UiColors.destructive.withOpacity(0.1);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withOpacity(0.2);
|
||||
textColor = UiColors.destructive;
|
||||
icon = UiIcons.warning;
|
||||
statusText = 'No Show';
|
||||
badgeBg = UiColors.destructive;
|
||||
badgeText = UiColors.destructiveForeground;
|
||||
badgeLabel = 'No Show';
|
||||
case CoverageWorkerStatus.completed:
|
||||
bg = UiColors.textSuccess.withOpacity(0.1);
|
||||
border = UiColors.textSuccess;
|
||||
textBg = UiColors.textSuccess.withOpacity(0.2);
|
||||
textColor = UiColors.textSuccess;
|
||||
icon = UiIcons.success;
|
||||
statusText = 'Completed';
|
||||
badgeBg = UiColors.textSuccess;
|
||||
badgeText = UiColors.primaryForeground;
|
||||
badgeLabel = 'Completed';
|
||||
case CoverageWorkerStatus.pending:
|
||||
case CoverageWorkerStatus.accepted:
|
||||
case CoverageWorkerStatus.rejected:
|
||||
bg = UiColors.muted.withOpacity(0.1);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withOpacity(0.2);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.clock;
|
||||
statusText = worker.status.name.toUpperCase();
|
||||
badgeBg = UiColors.muted;
|
||||
badgeText = UiColors.textPrimary;
|
||||
badgeLabel = worker.status.name[0].toUpperCase() +
|
||||
worker.status.name.substring(1);
|
||||
}
|
||||
|
||||
return Container(
|
||||
|
||||
@@ -43,14 +43,11 @@ class ReorderWidget extends StatelessWidget {
|
||||
),
|
||||
if (subtitle != null) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
Text(subtitle!, style: UiTypography.body2r.textSecondary),
|
||||
],
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
SizedBox(
|
||||
height: 140,
|
||||
height: 164,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: recentOrders.length,
|
||||
@@ -67,13 +64,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
border: Border.all(color: UiColors.border, width: 0.6),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -129,10 +120,7 @@ class ReorderWidget extends StatelessWidget {
|
||||
style: UiTypography.body1b,
|
||||
),
|
||||
Text(
|
||||
i18n.per_hr(
|
||||
amount: order.hourlyRate.toString(),
|
||||
) +
|
||||
' · ${order.hours}h',
|
||||
'${i18n.per_hr(amount: order.hourlyRate.toString())} · ${order.hours}h',
|
||||
style: UiTypography.footnote2r.textSecondary,
|
||||
),
|
||||
],
|
||||
@@ -145,49 +133,37 @@ class ReorderWidget extends StatelessWidget {
|
||||
_Badge(
|
||||
icon: UiIcons.success,
|
||||
text: order.type,
|
||||
color: const Color(0xFF2563EB),
|
||||
bg: const Color(0xFF2563EB),
|
||||
textColor: UiColors.white,
|
||||
color: UiColors.primary,
|
||||
bg: UiColors.buttonSecondaryStill,
|
||||
textColor: UiColors.primary,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
_Badge(
|
||||
icon: UiIcons.building,
|
||||
text: '${order.workers}',
|
||||
color: const Color(0xFF334155),
|
||||
bg: const Color(0xFFF1F5F9),
|
||||
textColor: const Color(0xFF334155),
|
||||
color: UiColors.textSecondary,
|
||||
bg: UiColors.buttonSecondaryStill,
|
||||
textColor: UiColors.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 28,
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => onReorderPressed(<String, dynamic>{
|
||||
'orderId': order.orderId,
|
||||
'title': order.title,
|
||||
'location': order.location,
|
||||
'hourlyRate': order.hourlyRate,
|
||||
'hours': order.hours,
|
||||
'workers': order.workers,
|
||||
'type': order.type,
|
||||
}),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.primary,
|
||||
foregroundColor: UiColors.white,
|
||||
padding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
icon: const Icon(UiIcons.zap, size: 12),
|
||||
label: Text(
|
||||
i18n.reorder_button,
|
||||
style: UiTypography.footnote1m,
|
||||
),
|
||||
),
|
||||
|
||||
UiButton.secondary(
|
||||
size: UiButtonSize.small,
|
||||
text: i18n.reorder_button,
|
||||
leadingIcon: UiIcons.zap,
|
||||
iconSize: 12,
|
||||
fullWidth: true,
|
||||
onPressed: () => onReorderPressed(<String, dynamic>{
|
||||
'orderId': order.orderId,
|
||||
'title': order.title,
|
||||
'location': order.location,
|
||||
'hourlyRate': order.hourlyRate,
|
||||
'hours': order.hours,
|
||||
'workers': order.workers,
|
||||
'type': order.type,
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -35,7 +35,7 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8),
|
||||
margin: const EdgeInsets.only(top: UiConstants.space16),
|
||||
margin: const EdgeInsets.only(top: UiConstants.space24),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:ui';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -10,6 +9,7 @@ import '../blocs/view_orders_cubit.dart';
|
||||
import '../blocs/view_orders_state.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../widgets/view_order_card.dart';
|
||||
import '../widgets/view_orders_header.dart';
|
||||
import '../navigation/view_orders_navigator.dart';
|
||||
|
||||
/// The main page for viewing client orders.
|
||||
@@ -22,6 +22,7 @@ class ViewOrdersPage extends StatelessWidget {
|
||||
/// Creates a [ViewOrdersPage].
|
||||
const ViewOrdersPage({super.key, this.initialDate});
|
||||
|
||||
/// The initial date to display orders for.
|
||||
final DateTime? initialDate;
|
||||
|
||||
@override
|
||||
@@ -37,7 +38,8 @@ class ViewOrdersPage extends StatelessWidget {
|
||||
class ViewOrdersView extends StatefulWidget {
|
||||
/// Creates a [ViewOrdersView].
|
||||
const ViewOrdersView({super.key, this.initialDate});
|
||||
|
||||
|
||||
/// The initial date to display orders for.
|
||||
final DateTime? initialDate;
|
||||
|
||||
@override
|
||||
@@ -88,376 +90,84 @@ class _ViewOrdersViewState extends State<ViewOrdersView> {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
// Background Gradient
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[UiColors.bgSecondary, UiColors.white],
|
||||
stops: <double>[0.0, 0.3],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
// Header + Filter + Calendar (Sticky behavior)
|
||||
ViewOrdersHeader(
|
||||
state: state,
|
||||
calendarDays: calendarDays,
|
||||
),
|
||||
),
|
||||
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
// Header + Filter + Calendar (Sticky behavior)
|
||||
_buildHeader(
|
||||
context: context,
|
||||
state: state,
|
||||
calendarDays: calendarDays,
|
||||
),
|
||||
|
||||
// Content List
|
||||
Expanded(
|
||||
child: filteredOrders.isEmpty
|
||||
? _buildEmptyState(context: context, state: state)
|
||||
: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space4,
|
||||
UiConstants.space5,
|
||||
100,
|
||||
),
|
||||
children: <Widget>[
|
||||
if (filteredOrders.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: UiConstants.space2,
|
||||
),
|
||||
Text(
|
||||
sectionTitle.toUpperCase(),
|
||||
style: UiTypography.titleUppercase2m
|
||||
.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: UiConstants.space1,
|
||||
),
|
||||
Text(
|
||||
'(${filteredOrders.length})',
|
||||
style: UiTypography.footnote1r
|
||||
.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
...filteredOrders.map(
|
||||
(OrderItem order) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: ViewOrderCard(order: order),
|
||||
),
|
||||
|
||||
// Content List
|
||||
Expanded(
|
||||
child: filteredOrders.isEmpty
|
||||
? _buildEmptyState(context: context, state: state)
|
||||
: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space4,
|
||||
UiConstants.space5,
|
||||
100,
|
||||
),
|
||||
children: <Widget>[
|
||||
if (filteredOrders.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
],
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: UiConstants.space2,
|
||||
),
|
||||
Text(
|
||||
sectionTitle.toUpperCase(),
|
||||
style: UiTypography.titleUppercase2m
|
||||
.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: UiConstants.space1,
|
||||
),
|
||||
Text(
|
||||
'(${filteredOrders.length})',
|
||||
style: UiTypography.footnote1r
|
||||
.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
...filteredOrders.map(
|
||||
(OrderItem order) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: ViewOrderCard(order: order),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the sticky header section.
|
||||
Widget _buildHeader({
|
||||
required BuildContext context,
|
||||
required ViewOrdersState state,
|
||||
required List<DateTime> calendarDays,
|
||||
}) {
|
||||
return ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xCCFFFFFF), // White with 0.8 alpha
|
||||
border: Border(
|
||||
bottom: BorderSide(color: UiColors.separatorSecondary),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
// Top Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space3,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_view_orders.title,
|
||||
style: UiTypography.headline3m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (state.filteredOrders.isNotEmpty)
|
||||
UiButton.primary(
|
||||
text: t.client_view_orders.post_button,
|
||||
leadingIcon: UiIcons.add,
|
||||
onPressed: () => Modular.to.navigateToCreateOrder(),
|
||||
size: UiButtonSize.small,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(0, 48),
|
||||
maximumSize: const Size(0, 48),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Filter Tabs
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
_buildFilterTab(
|
||||
context,
|
||||
label: t.client_view_orders.tabs.up_next,
|
||||
isSelected: state.filterTab == 'all',
|
||||
tabId: 'all',
|
||||
count: state.upNextCount,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space6),
|
||||
_buildFilterTab(
|
||||
context,
|
||||
label: t.client_view_orders.tabs.active,
|
||||
isSelected: state.filterTab == 'active',
|
||||
tabId: 'active',
|
||||
count: state.activeCount,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space6),
|
||||
_buildFilterTab(
|
||||
context,
|
||||
label: t.client_view_orders.tabs.completed,
|
||||
isSelected: state.filterTab == 'completed',
|
||||
tabId: 'completed',
|
||||
count: state.completedCount,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Header controls
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
UiIcons.chevronLeft,
|
||||
size: 20,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onPressed: () => BlocProvider.of<ViewOrdersCubit>(
|
||||
context,
|
||||
).updateWeekOffset(-1),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
splashRadius: 20,
|
||||
),
|
||||
Text(
|
||||
DateFormat('MMMM yyyy').format(calendarDays.first),
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
UiIcons.chevronRight,
|
||||
size: 20,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onPressed: () => BlocProvider.of<ViewOrdersCubit>(
|
||||
context,
|
||||
).updateWeekOffset(1),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
splashRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Grid
|
||||
SizedBox(
|
||||
height: 72,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: 7,
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final DateTime date = calendarDays[index];
|
||||
final bool isSelected =
|
||||
state.selectedDate != null &&
|
||||
date.year == state.selectedDate!.year &&
|
||||
date.month == state.selectedDate!.month &&
|
||||
date.day == state.selectedDate!.day;
|
||||
|
||||
// Check if this date has any shifts
|
||||
final String dateStr = DateFormat(
|
||||
'yyyy-MM-dd',
|
||||
).format(date);
|
||||
final bool hasShifts = state.orders.any(
|
||||
(OrderItem s) => s.date == dateStr,
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => BlocProvider.of<ViewOrdersCubit>(
|
||||
context,
|
||||
).selectDate(date),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiColors.primary : UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? UiColors.primary
|
||||
: UiColors.separatorPrimary,
|
||||
),
|
||||
boxShadow: isSelected
|
||||
? <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.primary.withValues(
|
||||
alpha: 0.25,
|
||||
),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
DateFormat('dd').format(date),
|
||||
style: UiTypography.title2b.copyWith(
|
||||
fontSize: 18,
|
||||
color: isSelected
|
||||
? UiColors.white
|
||||
: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateFormat('E').format(date),
|
||||
style: UiTypography.footnote2m.copyWith(
|
||||
color: isSelected
|
||||
? UiColors.white.withValues(alpha: 0.8)
|
||||
: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (hasShifts) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? UiColors.white
|
||||
: UiColors.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a single filter tab.
|
||||
Widget _buildFilterTab(
|
||||
BuildContext context, {
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required String tabId,
|
||||
int? count,
|
||||
}) {
|
||||
String text = label;
|
||||
if (count != null) {
|
||||
text = '$label ($count)';
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
BlocProvider.of<ViewOrdersCubit>(context).selectFilterTab(tabId),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space2),
|
||||
child: Text(
|
||||
text,
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: isSelected ? UiColors.primary : UiColors.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: 2,
|
||||
width: isSelected ? 40 : 0,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primary,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
if (!isSelected) const SizedBox(height: 2),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the empty state view.
|
||||
Widget _buildEmptyState({
|
||||
required BuildContext context,
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../blocs/view_orders_cubit.dart';
|
||||
|
||||
/// A rich card displaying details of a client order/shift.
|
||||
@@ -325,15 +326,23 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
if (order.workersNeeded != 0)
|
||||
if (coveragePercent != 100)
|
||||
const Icon(
|
||||
UiIcons.error,
|
||||
size: 16,
|
||||
color: UiColors.textError,
|
||||
),
|
||||
if (coveragePercent == 100)
|
||||
const Icon(
|
||||
UiIcons.checkCircle,
|
||||
size: 16,
|
||||
color: UiColors.textSuccess,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${order.workersNeeded} Workers Needed',
|
||||
coveragePercent == 100
|
||||
? 'All Workers Confirmed'
|
||||
: '${order.workersNeeded} Workers Needed',
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/view_orders_cubit.dart';
|
||||
|
||||
/// A single filter tab for the View Orders page.
|
||||
///
|
||||
/// Displays a label with an optional count and shows a selection indicator
|
||||
/// when the tab is active.
|
||||
class ViewOrdersFilterTab extends StatelessWidget {
|
||||
/// Creates a [ViewOrdersFilterTab].
|
||||
const ViewOrdersFilterTab({
|
||||
required this.label,
|
||||
required this.isSelected,
|
||||
required this.tabId,
|
||||
this.count,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The label text to display.
|
||||
final String label;
|
||||
|
||||
/// Whether this tab is currently selected.
|
||||
final bool isSelected;
|
||||
|
||||
/// The unique identifier for this tab.
|
||||
final String tabId;
|
||||
|
||||
/// Optional count to display next to the label.
|
||||
final int? count;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String text = label;
|
||||
if (count != null) {
|
||||
text = '$label ($count)';
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
BlocProvider.of<ViewOrdersCubit>(context).selectFilterTab(tabId),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space2),
|
||||
child: Text(
|
||||
text,
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: isSelected ? UiColors.primary : UiColors.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: 2,
|
||||
width: isSelected ? 40 : 0,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primary,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
if (!isSelected) const SizedBox(height: 2),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import 'dart:ui';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../blocs/view_orders_cubit.dart';
|
||||
import '../blocs/view_orders_state.dart';
|
||||
import '../navigation/view_orders_navigator.dart';
|
||||
import 'view_orders_filter_tab.dart';
|
||||
|
||||
/// The sticky header section for the View Orders page.
|
||||
///
|
||||
/// This widget contains:
|
||||
/// - Top bar with title and post button
|
||||
/// - Filter tabs (Up Next, Active, Completed)
|
||||
/// - Calendar navigation controls
|
||||
/// - Horizontal calendar grid
|
||||
class ViewOrdersHeader extends StatelessWidget {
|
||||
/// Creates a [ViewOrdersHeader].
|
||||
const ViewOrdersHeader({
|
||||
required this.state,
|
||||
required this.calendarDays,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The current state of the view orders feature.
|
||||
final ViewOrdersState state;
|
||||
|
||||
/// The list of calendar days to display.
|
||||
final List<DateTime> calendarDays;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xCCFFFFFF), // White with 0.8 alpha
|
||||
border: Border(
|
||||
bottom: BorderSide(color: UiColors.separatorSecondary),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
// Top Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space3,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_view_orders.title,
|
||||
style: UiTypography.headline3m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (state.filteredOrders.isNotEmpty)
|
||||
UiButton.primary(
|
||||
text: t.client_view_orders.post_button,
|
||||
leadingIcon: UiIcons.add,
|
||||
onPressed: () => Modular.to.navigateToCreateOrder(),
|
||||
size: UiButtonSize.small,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(0, 48),
|
||||
maximumSize: const Size(0, 48),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Filter Tabs
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ViewOrdersFilterTab(
|
||||
label: t.client_view_orders.tabs.up_next,
|
||||
isSelected: state.filterTab == 'all',
|
||||
tabId: 'all',
|
||||
count: state.upNextCount,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space6),
|
||||
ViewOrdersFilterTab(
|
||||
label: t.client_view_orders.tabs.active,
|
||||
isSelected: state.filterTab == 'active',
|
||||
tabId: 'active',
|
||||
count: state.activeCount,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space6),
|
||||
ViewOrdersFilterTab(
|
||||
label: t.client_view_orders.tabs.completed,
|
||||
isSelected: state.filterTab == 'completed',
|
||||
tabId: 'completed',
|
||||
count: state.completedCount,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Header controls
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
UiIcons.chevronLeft,
|
||||
size: 20,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onPressed: () => BlocProvider.of<ViewOrdersCubit>(
|
||||
context,
|
||||
).updateWeekOffset(-1),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
splashRadius: 20,
|
||||
),
|
||||
Text(
|
||||
DateFormat('MMMM yyyy').format(calendarDays.first),
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
UiIcons.chevronRight,
|
||||
size: 20,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
onPressed: () => BlocProvider.of<ViewOrdersCubit>(
|
||||
context,
|
||||
).updateWeekOffset(1),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
splashRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Calendar Grid
|
||||
SizedBox(
|
||||
height: 72,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: 7,
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final DateTime date = calendarDays[index];
|
||||
final bool isSelected =
|
||||
state.selectedDate != null &&
|
||||
date.year == state.selectedDate!.year &&
|
||||
date.month == state.selectedDate!.month &&
|
||||
date.day == state.selectedDate!.day;
|
||||
|
||||
// Check if this date has any shifts
|
||||
final String dateStr = DateFormat(
|
||||
'yyyy-MM-dd',
|
||||
).format(date);
|
||||
final bool hasShifts = state.orders.any(
|
||||
(OrderItem s) => s.date == dateStr,
|
||||
);
|
||||
|
||||
// Check if date is in the past
|
||||
final DateTime now = DateTime.now();
|
||||
final DateTime today = DateTime(now.year, now.month, now.day);
|
||||
final DateTime checkDate = DateTime(date.year, date.month, date.day);
|
||||
final bool isPast = checkDate.isBefore(today);
|
||||
|
||||
return Opacity(
|
||||
opacity: isPast && !isSelected ? 0.5 : 1.0,
|
||||
child: GestureDetector(
|
||||
onTap: () => BlocProvider.of<ViewOrdersCubit>(
|
||||
context,
|
||||
).selectDate(date),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiColors.primary : UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? UiColors.primary
|
||||
: UiColors.separatorPrimary,
|
||||
),
|
||||
boxShadow: isSelected
|
||||
? <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.primary.withValues(
|
||||
alpha: 0.25,
|
||||
),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
DateFormat('dd').format(date),
|
||||
style: UiTypography.title2b.copyWith(
|
||||
fontSize: 18,
|
||||
color: isSelected
|
||||
? UiColors.white
|
||||
: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateFormat('E').format(date),
|
||||
style: UiTypography.footnote2m.copyWith(
|
||||
color: isSelected
|
||||
? UiColors.white.withValues(alpha: 0.8)
|
||||
: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (hasShifts) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? UiColors.white
|
||||
: UiColors.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,14 @@ dependencies:
|
||||
path: ../../../domain
|
||||
krow_core:
|
||||
path: ../../../core
|
||||
krow_data_connect:
|
||||
path: ../../../data_connect
|
||||
# UI
|
||||
lucide_icons: ^0.257.0
|
||||
intl: ^0.20.1
|
||||
url_launcher: ^6.3.1
|
||||
firebase_data_connect: ^0.2.2+2
|
||||
firebase_auth: ^6.1.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../utils/test_phone_numbers.dart';
|
||||
import '../../domain/ui_entities/auth_mode.dart';
|
||||
|
||||
import '../../domain/repositories/auth_repository_interface.dart';
|
||||
@@ -41,7 +42,17 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
|
||||
await firebaseAuth.verifyPhoneNumber(
|
||||
phoneNumber: phoneNumber,
|
||||
verificationCompleted: (_) {
|
||||
verificationCompleted: (PhoneAuthCredential credential) {
|
||||
// Skip auto-verification for test numbers to allow manual code entry
|
||||
if (TestPhoneNumbers.isTestNumber(phoneNumber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For real numbers, we can support auto-verification if desired.
|
||||
// But since this method returns a verificationId for manual OTP entry,
|
||||
// we might not handle direct sign-in here unless the architecture changes.
|
||||
// Currently, we just ignore it for the completer flow,
|
||||
// or we could sign in directly if the credential is provided.
|
||||
},
|
||||
verificationFailed: (FirebaseAuthException e) {
|
||||
if (!completer.isCompleted) {
|
||||
@@ -168,7 +179,11 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
avatar: staffRecord.photoUrl,
|
||||
);
|
||||
StaffSessionStore.instance.setSession(
|
||||
StaffSession(user: domainUser, staff: domainStaff),
|
||||
StaffSession(
|
||||
user: domainUser,
|
||||
staff: domainStaff,
|
||||
ownerId: staffRecord?.ownerId,
|
||||
),
|
||||
);
|
||||
return domainUser;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
||||
|
||||
if (session != null) {
|
||||
StaffSessionStore.instance.setSession(
|
||||
StaffSession(user: session.user, staff: staff),
|
||||
StaffSession(user: session.user, staff: staff, ownerId: session.ownerId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_event.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_state.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
|
||||
import '../widgets/phone_verification_page/phone_input.dart';
|
||||
import '../widgets/phone_verification_page/otp_verification.dart';
|
||||
import 'package:staff_authentication/staff_authentication.dart';
|
||||
|
||||
import '../navigation/auth_navigator.dart'; // Import the extension
|
||||
import '../widgets/phone_verification_page/otp_verification.dart';
|
||||
import '../widgets/phone_verification_page/phone_input.dart';
|
||||
|
||||
/// A combined page for phone number entry and OTP verification.
|
||||
///
|
||||
|
||||
@@ -7,7 +7,6 @@ class GetStartedBackground extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 32),
|
||||
|
||||
@@ -9,15 +9,29 @@ import 'phone_input/phone_input_form_field.dart';
|
||||
import 'phone_input/phone_input_header.dart';
|
||||
|
||||
/// A widget that displays the phone number entry UI.
|
||||
class PhoneInput extends StatelessWidget {
|
||||
class PhoneInput extends StatefulWidget {
|
||||
/// Creates a [PhoneInput].
|
||||
const PhoneInput({super.key, required this.state, required this.onSendCode});
|
||||
|
||||
/// The current state of the authentication process.
|
||||
final AuthState state;
|
||||
|
||||
/// Callback for when the "Send Code" action is triggered.
|
||||
final VoidCallback onSendCode;
|
||||
|
||||
/// Creates a [PhoneInput].
|
||||
const PhoneInput({super.key, required this.state, required this.onSendCode});
|
||||
@override
|
||||
State<PhoneInput> createState() => _PhoneInputState();
|
||||
}
|
||||
|
||||
class _PhoneInputState extends State<PhoneInput> {
|
||||
void _handlePhoneChanged(String value) {
|
||||
if (!mounted) return;
|
||||
|
||||
final AuthBloc bloc = context.read<AuthBloc>();
|
||||
if (!bloc.isClosed) {
|
||||
bloc.add(AuthPhoneUpdated(value));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -35,19 +49,18 @@ class PhoneInput extends StatelessWidget {
|
||||
const PhoneInputHeader(),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
PhoneInputFormField(
|
||||
initialValue: state.phoneNumber,
|
||||
error: state.errorMessage ?? '',
|
||||
onChanged: (String value) {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthPhoneUpdated(value));
|
||||
},
|
||||
initialValue: widget.state.phoneNumber,
|
||||
error: widget.state.errorMessage ?? '',
|
||||
onChanged: _handlePhoneChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
PhoneInputActions(isLoading: state.isLoading, onSendCode: onSendCode),
|
||||
PhoneInputActions(
|
||||
isLoading: widget.state.isLoading,
|
||||
onSendCode: widget.onSendCode,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class TestPhoneNumbers {
|
||||
static const List<String> values = <String>[
|
||||
'+15145912311', // Test User 1
|
||||
];
|
||||
|
||||
static bool isTestNumber(String phoneNumber) {
|
||||
return values.contains(phoneNumber);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +1,197 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/src/session/staff_session_store.dart';
|
||||
import '../../domain/repositories/clock_in_repository_interface.dart';
|
||||
|
||||
/// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect.
|
||||
class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
final firebase.FirebaseAuth _firebaseAuth;
|
||||
final Map<String, String> _shiftToApplicationId = {};
|
||||
|
||||
ClockInRepositoryImpl({
|
||||
required dc.ExampleConnector dataConnect,
|
||||
required firebase.FirebaseAuth firebaseAuth,
|
||||
}) : _dataConnect = dataConnect,
|
||||
_firebaseAuth = firebaseAuth;
|
||||
}) : _dataConnect = dataConnect;
|
||||
|
||||
Future<String> _getStaffId() async {
|
||||
final firebase.User? user = _firebaseAuth.currentUser;
|
||||
if (user == null) throw Exception('User not authenticated');
|
||||
|
||||
final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> result =
|
||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
||||
if (result.data.staffs.isEmpty) {
|
||||
throw Exception('Staff profile not found');
|
||||
final StaffSession? session = StaffSessionStore.instance.session;
|
||||
final String? staffId = session?.staff?.id;
|
||||
if (staffId != null && staffId.isNotEmpty) {
|
||||
return staffId;
|
||||
}
|
||||
return result.data.staffs.first.id;
|
||||
throw Exception('Staff session not found');
|
||||
}
|
||||
|
||||
/// Helper to convert Data Connect Timestamp to DateTime
|
||||
DateTime? _toDateTime(dynamic t) {
|
||||
if (t == null) return null;
|
||||
// Attempt to use toJson assuming it matches the generated code's expectation of String
|
||||
try {
|
||||
// If t has toDate (e.g. cloud_firestore), usage would be t.toDate()
|
||||
// But here we rely on toJson or toString
|
||||
return DateTime.tryParse(t.toJson() as String);
|
||||
} catch (_) {
|
||||
DateTime? dt;
|
||||
if (t is DateTime) {
|
||||
dt = t;
|
||||
} else if (t is String) {
|
||||
dt = DateTime.tryParse(t);
|
||||
} else {
|
||||
try {
|
||||
return DateTime.tryParse(t.toString());
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if (t is Timestamp) {
|
||||
dt = t.toDateTime();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
if (dt == null && t.runtimeType.toString().contains('Timestamp')) {
|
||||
dt = (t as dynamic).toDate();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
if (dt == null) {
|
||||
dt = DateTime.tryParse(t.toString());
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (dt != null) {
|
||||
return DateTimeUtils.toDeviceTime(dt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Helper to create Timestamp from DateTime
|
||||
Timestamp _fromDateTime(DateTime d) {
|
||||
// Assuming Timestamp.fromJson takes an ISO string
|
||||
return Timestamp.fromJson(d.toIso8601String());
|
||||
return Timestamp.fromJson(d.toUtc().toIso8601String());
|
||||
}
|
||||
|
||||
/// Helper to find today's active application
|
||||
Future<dc.GetApplicationsByStaffIdApplications?> _getTodaysApplication(String staffId) async {
|
||||
({Timestamp start, Timestamp end}) _utcDayRange(DateTime localDay) {
|
||||
final DateTime dayStartUtc = DateTime.utc(
|
||||
localDay.year,
|
||||
localDay.month,
|
||||
localDay.day,
|
||||
);
|
||||
final DateTime dayEndUtc = DateTime.utc(
|
||||
localDay.year,
|
||||
localDay.month,
|
||||
localDay.day,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
999,
|
||||
999,
|
||||
);
|
||||
return (
|
||||
start: _fromDateTime(dayStartUtc),
|
||||
end: _fromDateTime(dayEndUtc),
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper to find today's applications ordered with the closest at the end.
|
||||
Future<List<dc.GetApplicationsByStaffIdApplications>> _getTodaysApplications(
|
||||
String staffId,
|
||||
) async {
|
||||
final DateTime now = DateTime.now();
|
||||
|
||||
// Fetch recent applications (assuming meaningful limit)
|
||||
final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> result =
|
||||
await _dataConnect.getApplicationsByStaffId(
|
||||
staffId: staffId,
|
||||
).limit(20).execute();
|
||||
final range = _utcDayRange(now);
|
||||
final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables>
|
||||
result = await _dataConnect
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.dayStart(range.start)
|
||||
.dayEnd(range.end)
|
||||
.execute();
|
||||
|
||||
final apps = result.data.applications;
|
||||
if (apps.isEmpty) return const [];
|
||||
|
||||
_shiftToApplicationId
|
||||
..clear()
|
||||
..addEntries(apps.map((app) => MapEntry(app.shiftId, app.id)));
|
||||
|
||||
apps.sort((a, b) {
|
||||
final DateTime? aTime =
|
||||
_toDateTime(a.shift.startTime) ?? _toDateTime(a.shift.date);
|
||||
final DateTime? bTime =
|
||||
_toDateTime(b.shift.startTime) ?? _toDateTime(b.shift.date);
|
||||
if (aTime == null && bTime == null) return 0;
|
||||
if (aTime == null) return -1;
|
||||
if (bTime == null) return 1;
|
||||
final Duration aDiff = aTime.difference(now).abs();
|
||||
final Duration bDiff = bTime.difference(now).abs();
|
||||
return bDiff.compareTo(aDiff); // closest at the end
|
||||
});
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
dc.GetApplicationsByStaffIdApplications? _getActiveApplication(
|
||||
List<dc.GetApplicationsByStaffIdApplications> apps,
|
||||
) {
|
||||
try {
|
||||
return result.data.applications.firstWhere((dc.GetApplicationsByStaffIdApplications app) {
|
||||
final DateTime? shiftTime = _toDateTime(app.shift.startTime);
|
||||
|
||||
if (shiftTime == null) return false;
|
||||
|
||||
final bool isSameDay = shiftTime.year == now.year &&
|
||||
shiftTime.month == now.month &&
|
||||
shiftTime.day == now.day;
|
||||
|
||||
if (!isSameDay) return false;
|
||||
|
||||
// Check Status
|
||||
final dynamic status = app.status.stringValue;
|
||||
return status != 'PENDING' && status != 'REJECTED' && status != 'NO_SHOW' && status != 'CANCELED';
|
||||
return apps.firstWhere((app) {
|
||||
final status = app.status.stringValue;
|
||||
return status == 'CHECKED_IN' || status == 'LATE';
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Shift?> getTodaysShift() async {
|
||||
Future<List<Shift>> getTodaysShifts() async {
|
||||
final String staffId = await _getStaffId();
|
||||
final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId);
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
if (apps.isEmpty) return const [];
|
||||
|
||||
if (app == null) return null;
|
||||
final List<Shift> shifts = [];
|
||||
for (final app in apps) {
|
||||
final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift;
|
||||
final DateTime? startDt = _toDateTime(app.shiftRole.startTime);
|
||||
final DateTime? endDt = _toDateTime(app.shiftRole.endTime);
|
||||
final DateTime? createdDt = _toDateTime(app.createdAt);
|
||||
|
||||
final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift;
|
||||
|
||||
final QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> shiftResult =
|
||||
await _dataConnect.getShiftById(id: shift.id).execute();
|
||||
|
||||
if (shiftResult.data.shift == null) return null;
|
||||
|
||||
final dc.GetShiftByIdShift fullShift = shiftResult.data.shift!;
|
||||
|
||||
return Shift(
|
||||
id: fullShift.id,
|
||||
title: fullShift.title,
|
||||
clientName: fullShift.order.business.businessName,
|
||||
logoUrl: '', // Not available in GetShiftById
|
||||
hourlyRate: 0.0,
|
||||
location: fullShift.location ?? '',
|
||||
locationAddress: fullShift.locationAddress ?? '',
|
||||
date: _toDateTime(fullShift.startTime)?.toIso8601String() ?? '',
|
||||
startTime: _toDateTime(fullShift.startTime)?.toIso8601String() ?? '',
|
||||
endTime: _toDateTime(fullShift.endTime)?.toIso8601String() ?? '',
|
||||
createdDate: _toDateTime(fullShift.createdAt)?.toIso8601String() ?? '',
|
||||
status: fullShift.status?.stringValue,
|
||||
description: fullShift.description,
|
||||
);
|
||||
final String roleName = app.shiftRole.role.name;
|
||||
final String orderName =
|
||||
(shift.order.eventName ?? '').trim().isNotEmpty
|
||||
? shift.order.eventName!
|
||||
: shift.order.business.businessName;
|
||||
final String title = '$roleName - $orderName';
|
||||
shifts.add(
|
||||
Shift(
|
||||
id: shift.id,
|
||||
title: title,
|
||||
clientName: shift.order.business.businessName,
|
||||
logoUrl: shift.order.business.companyLogoUrl ?? '',
|
||||
hourlyRate: app.shiftRole.role.costPerHour,
|
||||
location: shift.location ?? '',
|
||||
locationAddress: shift.order.teamHub.hubName,
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt?.toIso8601String() ?? '',
|
||||
endTime: endDt?.toIso8601String() ?? '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: shift.status?.stringValue,
|
||||
description: shift.description,
|
||||
latitude: shift.latitude,
|
||||
longitude: shift.longitude,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return shifts;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AttendanceStatus> getAttendanceStatus() async {
|
||||
final String staffId = await _getStaffId();
|
||||
final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId);
|
||||
|
||||
if (app == null) {
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
if (apps.isEmpty) {
|
||||
return const AttendanceStatus(isCheckedIn: false);
|
||||
}
|
||||
|
||||
final dc.GetApplicationsByStaffIdApplications? activeApp =
|
||||
_getActiveApplication(apps);
|
||||
final dc.GetApplicationsByStaffIdApplications app =
|
||||
activeApp ?? apps.last;
|
||||
|
||||
return ClockInAdapter.toAttendanceStatus(
|
||||
status: app.status.stringValue,
|
||||
checkInTime: _toDateTime(app.checkInTime),
|
||||
@@ -134,19 +203,25 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
@override
|
||||
Future<AttendanceStatus> clockIn({required String shiftId, String? notes}) async {
|
||||
final String staffId = await _getStaffId();
|
||||
|
||||
final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> appsResult =
|
||||
await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute();
|
||||
|
||||
final dc.GetApplicationsByStaffIdApplications app = appsResult.data.applications.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId);
|
||||
|
||||
await _dataConnect.updateApplicationStatus(
|
||||
id: app.id,
|
||||
roleId: app.shiftRole.id,
|
||||
)
|
||||
.status(dc.ApplicationStatus.CHECKED_IN)
|
||||
.checkInTime(_fromDateTime(DateTime.now()))
|
||||
.execute();
|
||||
|
||||
final String? cachedAppId = _shiftToApplicationId[shiftId];
|
||||
dc.GetApplicationsByStaffIdApplications? app;
|
||||
if (cachedAppId != null) {
|
||||
try {
|
||||
final apps = await _getTodaysApplications(staffId);
|
||||
app = apps.firstWhere((a) => a.id == cachedAppId);
|
||||
} catch (_) {}
|
||||
}
|
||||
app ??= (await _getTodaysApplications(staffId))
|
||||
.firstWhere((a) => a.shiftId == shiftId);
|
||||
|
||||
await _dataConnect
|
||||
.updateApplicationStatus(
|
||||
id: app.id,
|
||||
roleId: app.shiftRole.id,
|
||||
)
|
||||
.checkInTime(_fromDateTime(DateTime.now()))
|
||||
.execute();
|
||||
|
||||
return getAttendanceStatus();
|
||||
}
|
||||
@@ -155,7 +230,10 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}) async {
|
||||
final String staffId = await _getStaffId();
|
||||
|
||||
final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId);
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
final dc.GetApplicationsByStaffIdApplications? app =
|
||||
_getActiveApplication(apps);
|
||||
if (app == null) throw Exception('No active shift found to clock out');
|
||||
|
||||
await _dataConnect.updateApplicationStatus(
|
||||
@@ -168,10 +246,4 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
|
||||
return getAttendanceStatus();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> getActivityLog() async {
|
||||
// Placeholder as this wasn't main focus and returns raw maps
|
||||
return <Map<String, dynamic>>[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
/// Repository interface for Clock In/Out functionality
|
||||
abstract class ClockInRepositoryInterface {
|
||||
|
||||
/// Retrieves the shift assigned to the user for the current day.
|
||||
/// Returns null if no shift is assigned for today.
|
||||
Future<Shift?> getTodaysShift();
|
||||
/// Retrieves the shifts assigned to the user for the current day.
|
||||
/// Returns empty list if no shift is assigned for today.
|
||||
Future<List<Shift>> getTodaysShifts();
|
||||
|
||||
/// Gets the current attendance status (e.g., checked in or not, times).
|
||||
/// This helps in restoring the UI state if the app was killed.
|
||||
@@ -18,7 +18,4 @@ abstract class ClockInRepositoryInterface {
|
||||
/// Checks the user out for the currently active shift.
|
||||
/// Optionally accepts [breakTimeMinutes] if tracked.
|
||||
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes});
|
||||
|
||||
/// Retrieves a list of recent clock-in/out activities.
|
||||
Future<List<Map<String, dynamic>>> getActivityLog();
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../repositories/clock_in_repository_interface.dart';
|
||||
|
||||
/// Use case for retrieving the activity log.
|
||||
class GetActivityLogUseCase implements NoInputUseCase<List<Map<String, dynamic>>> {
|
||||
final ClockInRepositoryInterface _repository;
|
||||
|
||||
GetActivityLogUseCase(this._repository);
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> call() {
|
||||
return _repository.getActivityLog();
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../repositories/clock_in_repository_interface.dart';
|
||||
|
||||
/// Use case for retrieving the user's scheduled shift for today.
|
||||
class GetTodaysShiftUseCase implements NoInputUseCase<Shift?> {
|
||||
/// Use case for retrieving the user's scheduled shifts for today.
|
||||
class GetTodaysShiftUseCase implements NoInputUseCase<List<Shift>> {
|
||||
final ClockInRepositoryInterface _repository;
|
||||
|
||||
GetTodaysShiftUseCase(this._repository);
|
||||
|
||||
@override
|
||||
Future<Shift?> call() {
|
||||
return _repository.getTodaysShift();
|
||||
Future<List<Shift>> call() {
|
||||
return _repository.getTodaysShifts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_todays_shift_usecase.dart';
|
||||
import '../../domain/usecases/get_attendance_status_usecase.dart';
|
||||
import '../../domain/usecases/clock_in_usecase.dart';
|
||||
import '../../domain/usecases/clock_out_usecase.dart';
|
||||
import '../../domain/usecases/get_activity_log_usecase.dart';
|
||||
import '../../domain/arguments/clock_in_arguments.dart';
|
||||
import '../../domain/arguments/clock_out_arguments.dart';
|
||||
import 'clock_in_event.dart';
|
||||
@@ -15,11 +15,8 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
final GetAttendanceStatusUseCase _getAttendanceStatus;
|
||||
final ClockInUseCase _clockIn;
|
||||
final ClockOutUseCase _clockOut;
|
||||
final GetActivityLogUseCase _getActivityLog;
|
||||
|
||||
// Mock Venue Location (e.g., Grand Hotel, NYC)
|
||||
static const double venueLat = 40.7128;
|
||||
static const double venueLng = -74.0060;
|
||||
static const double allowedRadiusMeters = 500;
|
||||
|
||||
ClockInBloc({
|
||||
@@ -27,14 +24,13 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
required GetAttendanceStatusUseCase getAttendanceStatus,
|
||||
required ClockInUseCase clockIn,
|
||||
required ClockOutUseCase clockOut,
|
||||
required GetActivityLogUseCase getActivityLog,
|
||||
}) : _getTodaysShift = getTodaysShift,
|
||||
_getAttendanceStatus = getAttendanceStatus,
|
||||
_clockIn = clockIn,
|
||||
_clockOut = clockOut,
|
||||
_getActivityLog = getActivityLog,
|
||||
super(ClockInState(selectedDate: DateTime.now())) {
|
||||
on<ClockInPageLoaded>(_onLoaded);
|
||||
on<ShiftSelected>(_onShiftSelected);
|
||||
on<DateSelected>(_onDateSelected);
|
||||
on<CheckInRequested>(_onCheckIn);
|
||||
on<CheckOutRequested>(_onCheckOut);
|
||||
@@ -52,21 +48,30 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
) async {
|
||||
emit(state.copyWith(status: ClockInStatus.loading));
|
||||
try {
|
||||
final shift = await _getTodaysShift();
|
||||
final shifts = await _getTodaysShift();
|
||||
final status = await _getAttendanceStatus();
|
||||
final activity = await _getActivityLog();
|
||||
|
||||
// Check permissions silently on load? Maybe better to wait for user interaction or specific event
|
||||
// However, if shift exists, we might want to check permission state
|
||||
|
||||
Shift? selectedShift;
|
||||
if (shifts.isNotEmpty) {
|
||||
if (status.activeShiftId != null) {
|
||||
try {
|
||||
selectedShift =
|
||||
shifts.firstWhere((s) => s.id == status.activeShiftId);
|
||||
} catch (_) {}
|
||||
}
|
||||
selectedShift ??= shifts.last;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
status: ClockInStatus.success,
|
||||
todayShift: shift,
|
||||
todayShifts: shifts,
|
||||
selectedShift: selectedShift,
|
||||
attendance: status,
|
||||
activityLog: activity,
|
||||
));
|
||||
|
||||
if (shift != null && !status.isCheckedIn) {
|
||||
if (selectedShift != null && !status.isCheckedIn) {
|
||||
add(RequestLocationPermission());
|
||||
}
|
||||
|
||||
@@ -103,13 +108,25 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
void _startLocationUpdates() async {
|
||||
try {
|
||||
final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
|
||||
final distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
venueLat,
|
||||
venueLng,
|
||||
);
|
||||
final isVerified = distance <= allowedRadiusMeters;
|
||||
|
||||
double distance = 0;
|
||||
bool isVerified = false; // Require location match by default if shift has location
|
||||
|
||||
if (state.selectedShift != null &&
|
||||
state.selectedShift!.latitude != null &&
|
||||
state.selectedShift!.longitude != null) {
|
||||
distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
state.selectedShift!.latitude!,
|
||||
state.selectedShift!.longitude!,
|
||||
);
|
||||
isVerified = distance <= allowedRadiusMeters;
|
||||
} else {
|
||||
// If no shift location, assume verified or don't restrict?
|
||||
// For strict clock-in, maybe false? but let's default to verified to avoid blocking if data missing
|
||||
isVerified = true;
|
||||
}
|
||||
|
||||
if (!isClosed) {
|
||||
add(LocationUpdated(position: position, distance: distance, isVerified: isVerified));
|
||||
@@ -141,6 +158,16 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _onShiftSelected(
|
||||
ShiftSelected event,
|
||||
Emitter<ClockInState> emit,
|
||||
) {
|
||||
emit(state.copyWith(selectedShift: event.shift));
|
||||
if (!state.attendance.isCheckedIn) {
|
||||
_startLocationUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
void _onDateSelected(
|
||||
DateSelected event,
|
||||
Emitter<ClockInState> emit,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
abstract class ClockInEvent extends Equatable {
|
||||
const ClockInEvent();
|
||||
@@ -10,6 +11,14 @@ abstract class ClockInEvent extends Equatable {
|
||||
|
||||
class ClockInPageLoaded extends ClockInEvent {}
|
||||
|
||||
class ShiftSelected extends ClockInEvent {
|
||||
final Shift shift;
|
||||
const ShiftSelected(this.shift);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [shift];
|
||||
}
|
||||
|
||||
class DateSelected extends ClockInEvent {
|
||||
final DateTime date;
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ enum ClockInStatus { initial, loading, success, failure, actionInProgress }
|
||||
|
||||
class ClockInState extends Equatable {
|
||||
final ClockInStatus status;
|
||||
final Shift? todayShift;
|
||||
final List<Shift> todayShifts;
|
||||
final Shift? selectedShift;
|
||||
final AttendanceStatus attendance;
|
||||
final List<Map<String, dynamic>> activityLog;
|
||||
final DateTime selectedDate;
|
||||
final String checkInMode;
|
||||
final String? errorMessage;
|
||||
@@ -23,9 +23,9 @@ class ClockInState extends Equatable {
|
||||
|
||||
const ClockInState({
|
||||
this.status = ClockInStatus.initial,
|
||||
this.todayShift,
|
||||
this.todayShifts = const [],
|
||||
this.selectedShift,
|
||||
this.attendance = const AttendanceStatus(),
|
||||
this.activityLog = const [],
|
||||
required this.selectedDate,
|
||||
this.checkInMode = 'swipe',
|
||||
this.errorMessage,
|
||||
@@ -39,9 +39,9 @@ class ClockInState extends Equatable {
|
||||
|
||||
ClockInState copyWith({
|
||||
ClockInStatus? status,
|
||||
Shift? todayShift,
|
||||
List<Shift>? todayShifts,
|
||||
Shift? selectedShift,
|
||||
AttendanceStatus? attendance,
|
||||
List<Map<String, dynamic>>? activityLog,
|
||||
DateTime? selectedDate,
|
||||
String? checkInMode,
|
||||
String? errorMessage,
|
||||
@@ -54,9 +54,9 @@ class ClockInState extends Equatable {
|
||||
}) {
|
||||
return ClockInState(
|
||||
status: status ?? this.status,
|
||||
todayShift: todayShift ?? this.todayShift,
|
||||
todayShifts: todayShifts ?? this.todayShifts,
|
||||
selectedShift: selectedShift ?? this.selectedShift,
|
||||
attendance: attendance ?? this.attendance,
|
||||
activityLog: activityLog ?? this.activityLog,
|
||||
selectedDate: selectedDate ?? this.selectedDate,
|
||||
checkInMode: checkInMode ?? this.checkInMode,
|
||||
errorMessage: errorMessage,
|
||||
@@ -72,9 +72,9 @@ class ClockInState extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
status,
|
||||
todayShift,
|
||||
todayShifts,
|
||||
selectedShift,
|
||||
attendance,
|
||||
activityLog,
|
||||
selectedDate,
|
||||
checkInMode,
|
||||
errorMessage,
|
||||
|
||||
@@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
import '../bloc/clock_in_bloc.dart';
|
||||
import '../bloc/clock_in_event.dart';
|
||||
import '../bloc/clock_in_state.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
import '../widgets/attendance_card.dart';
|
||||
import '../widgets/commute_tracker.dart';
|
||||
import '../widgets/date_selector.dart';
|
||||
import '../widgets/lunch_break_modal.dart';
|
||||
@@ -46,16 +46,23 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state.status == ClockInStatus.loading &&
|
||||
state.todayShift == null) {
|
||||
state.todayShifts.isEmpty) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
final todayShift = state.todayShift;
|
||||
final checkInTime = state.attendance.checkInTime;
|
||||
final checkOutTime = state.attendance.checkOutTime;
|
||||
final isCheckedIn = state.attendance.isCheckedIn;
|
||||
final todayShifts = state.todayShifts;
|
||||
final selectedShift = state.selectedShift;
|
||||
final activeShiftId = state.attendance.activeShiftId;
|
||||
final bool isActiveSelected =
|
||||
selectedShift != null && selectedShift.id == activeShiftId;
|
||||
final checkInTime =
|
||||
isActiveSelected ? state.attendance.checkInTime : null;
|
||||
final checkOutTime =
|
||||
isActiveSelected ? state.attendance.checkOutTime : null;
|
||||
final isCheckedIn =
|
||||
state.attendance.isCheckedIn && isActiveSelected;
|
||||
|
||||
// Format times for display
|
||||
final checkInStr = checkInTime != null
|
||||
@@ -89,9 +96,9 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Commute Tracker (shows before date selector when applicable)
|
||||
if (todayShift != null)
|
||||
if (selectedShift != null)
|
||||
CommuteTracker(
|
||||
shift: todayShift,
|
||||
shift: selectedShift,
|
||||
hasLocationConsent: state.hasLocationConsent,
|
||||
isCommuteModeOn: state.isCommuteModeOn,
|
||||
distanceMeters: state.distanceFromVenue,
|
||||
@@ -125,92 +132,113 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Selected Shift Info Card
|
||||
if (todayShift != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFE2E8F0),
|
||||
), // slate-200
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"TODAY'S SHIFT",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.krowBlue,
|
||||
letterSpacing: 0.5,
|
||||
if (todayShifts.isNotEmpty)
|
||||
Column(
|
||||
children: todayShifts
|
||||
.map(
|
||||
(shift) => GestureDetector(
|
||||
onTap: () =>
|
||||
_bloc.add(ShiftSelected(shift)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin:
|
||||
const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
12,
|
||||
),
|
||||
border: Border.all(
|
||||
color: shift.id ==
|
||||
selectedShift?.id
|
||||
? AppColors.krowBlue
|
||||
: const Color(0xFFE2E8F0),
|
||||
width:
|
||||
shift.id == selectedShift?.id
|
||||
? 2
|
||||
: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
todayShift.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(
|
||||
0xFF1E293B,
|
||||
), // slate-800
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
shift.id ==
|
||||
selectedShift?.id
|
||||
? "SELECTED SHIFT"
|
||||
: "TODAY'S SHIFT",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: shift.id ==
|
||||
selectedShift?.id
|
||||
? AppColors.krowBlue
|
||||
: AppColors
|
||||
.krowCharcoal,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
shift.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${shift.clientName} • ${shift.location}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF475569),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"\$${shift.hourlyRate}/hr",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.krowBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
"${todayShift.clientName} • ${todayShift.location}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(
|
||||
0xFF64748B,
|
||||
), // slate-500
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${_formatTime(todayShift.startTime)} - ${_formatTime(todayShift.endTime)}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF475569), // slate-600
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"\$${todayShift.hourlyRate}/hr",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.krowBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
|
||||
// Swipe To Check In / Checked Out State / No Shift State
|
||||
if (todayShift != null && checkOutTime == null) ...[
|
||||
if (!isCheckedIn && !_isCheckInAllowed(todayShift))
|
||||
if (selectedShift != null && checkOutTime == null) ...[
|
||||
if (!isCheckedIn &&
|
||||
!_isCheckInAllowed(selectedShift))
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
@@ -236,7 +264,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Check-in available at ${_getCheckInAvailabilityTime(todayShift)}",
|
||||
"Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}",
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF64748B), // slate-500
|
||||
@@ -259,7 +287,9 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
await _showNFCDialog(context);
|
||||
} else {
|
||||
_bloc.add(
|
||||
CheckInRequested(shiftId: todayShift.id),
|
||||
CheckInRequested(
|
||||
shiftId: selectedShift.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -277,7 +307,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
);
|
||||
},
|
||||
),
|
||||
] else if (todayShift != null &&
|
||||
] else if (selectedShift != null &&
|
||||
checkOutTime != null) ...[
|
||||
// Shift Completed State
|
||||
Container(
|
||||
@@ -417,78 +447,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Recent Activity List
|
||||
if (state.activityLog.isNotEmpty)
|
||||
...state.activityLog.map(
|
||||
(activity) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8F9FA),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
), // slate-100
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.krowBlue.withOpacity(
|
||||
0.1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.mapPin,
|
||||
color: AppColors.krowBlue,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('MMM d').format(
|
||||
activity['date'] as DateTime,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(
|
||||
0xFF0F172A,
|
||||
), // slate-900
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${activity['start']} - ${activity['end']}",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(
|
||||
0xFF64748B,
|
||||
), // slate-500
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
activity['hours'] as String,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.krowBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Recent Activity List (Temporarily removed)
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
@@ -552,87 +511,6 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: AppColors.krowBlue.withOpacity(0.2),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: AppColors.krowBlue.withOpacity(0.1),
|
||||
child: const Text(
|
||||
'K',
|
||||
style: TextStyle(
|
||||
color: AppColors.krowBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Good Morning',
|
||||
style: TextStyle(color: AppColors.krowMuted, fontSize: 12),
|
||||
),
|
||||
Text(
|
||||
'Krower',
|
||||
style: TextStyle(
|
||||
color: AppColors.krowCharcoal,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Warehouse Assistant',
|
||||
style: TextStyle(color: AppColors.krowMuted, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
20,
|
||||
), // Rounded full for this page per design
|
||||
border: Border.all(color: Colors.grey.shade100),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.bell,
|
||||
color: AppColors.krowMuted,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showNFCDialog(BuildContext context) async {
|
||||
bool scanned = false;
|
||||
|
||||
@@ -731,31 +609,40 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
|
||||
// After dialog closes, trigger the event if scan was successful (simulated)
|
||||
// In real app, we would check the dialog result
|
||||
if (scanned && _bloc.state.todayShift != null) {
|
||||
_bloc.add(CheckInRequested(shiftId: _bloc.state.todayShift!.id));
|
||||
if (scanned && _bloc.state.selectedShift != null) {
|
||||
_bloc.add(CheckInRequested(shiftId: _bloc.state.selectedShift!.id));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper Methods ---
|
||||
|
||||
String _formatTime(String timeStr) {
|
||||
// Expecting HH:mm or HH:mm:ss
|
||||
if (timeStr.isEmpty) return '';
|
||||
try {
|
||||
if (timeStr.isEmpty) return '';
|
||||
final parts = timeStr.split(':');
|
||||
final dt = DateTime(2022, 1, 1, int.parse(parts[0]), int.parse(parts[1]));
|
||||
// Try parsing as ISO string first (which contains date)
|
||||
final dt = DateTime.parse(timeStr);
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
} catch (e) {
|
||||
return timeStr;
|
||||
} catch (_) {
|
||||
// Fallback for strict "HH:mm" or "HH:mm:ss" strings
|
||||
try {
|
||||
final parts = timeStr.split(':');
|
||||
if (parts.length >= 2) {
|
||||
final dt = DateTime(2022, 1, 1, int.parse(parts[0]), int.parse(parts[1]));
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
}
|
||||
return timeStr;
|
||||
} catch (e) {
|
||||
return timeStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isCheckInAllowed(dynamic shift) {
|
||||
bool _isCheckInAllowed(Shift shift) {
|
||||
if (shift == null) return false;
|
||||
try {
|
||||
// Parse shift date (e.g. 2024-01-31T09:00:00)
|
||||
// The Shift entity has 'date' which is the start DateTime string
|
||||
final shiftStart = DateTime.parse(shift.date);
|
||||
final shiftStart = DateTime.parse(shift.startTime);
|
||||
final windowStart = shiftStart.subtract(const Duration(minutes: 15));
|
||||
return DateTime.now().isAfter(windowStart);
|
||||
} catch (e) {
|
||||
@@ -764,10 +651,10 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
}
|
||||
}
|
||||
|
||||
String _getCheckInAvailabilityTime(dynamic shift) {
|
||||
String _getCheckInAvailabilityTime(Shift shift) {
|
||||
if (shift == null) return '';
|
||||
try {
|
||||
final shiftStart = DateTime.parse(shift.date);
|
||||
final shiftStart = DateTime.parse(shift.startTime.trim());
|
||||
final windowStart = shiftStart.subtract(const Duration(minutes: 15));
|
||||
return DateFormat('h:mm a').format(windowStart);
|
||||
} catch (e) {
|
||||
|
||||
@@ -66,9 +66,14 @@ class _CommuteTrackerState extends State<CommuteTracker> {
|
||||
|
||||
// For demo purposes, check if we're within 24 hours of shift
|
||||
final now = DateTime.now();
|
||||
final shiftStart = DateTime.parse(
|
||||
'${widget.shift!.date} ${widget.shift!.startTime}',
|
||||
);
|
||||
DateTime shiftStart;
|
||||
try {
|
||||
shiftStart = DateTime.parse(widget.shift!.startTime);
|
||||
} catch (_) {
|
||||
shiftStart = DateTime.parse(
|
||||
'${widget.shift!.date} ${widget.shift!.startTime}',
|
||||
);
|
||||
}
|
||||
final hoursUntilShift = shiftStart.difference(now).inHours;
|
||||
final inCommuteWindow = hoursUntilShift <= 24 && hoursUntilShift >= 0;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
|
||||
@@ -6,7 +5,6 @@ import 'data/repositories_impl/clock_in_repository_impl.dart';
|
||||
import 'domain/repositories/clock_in_repository_interface.dart';
|
||||
import 'domain/usecases/clock_in_usecase.dart';
|
||||
import 'domain/usecases/clock_out_usecase.dart';
|
||||
import 'domain/usecases/get_activity_log_usecase.dart';
|
||||
import 'domain/usecases/get_attendance_status_usecase.dart';
|
||||
import 'domain/usecases/get_todays_shift_usecase.dart';
|
||||
import 'presentation/bloc/clock_in_bloc.dart';
|
||||
@@ -19,7 +17,6 @@ class StaffClockInModule extends Module {
|
||||
i.add<ClockInRepositoryInterface>(
|
||||
() => ClockInRepositoryImpl(
|
||||
dataConnect: ExampleConnector.instance,
|
||||
firebaseAuth: FirebaseAuth.instance,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -28,7 +25,6 @@ class StaffClockInModule extends Module {
|
||||
i.add<GetAttendanceStatusUseCase>(GetAttendanceStatusUseCase.new);
|
||||
i.add<ClockInUseCase>(ClockInUseCase.new);
|
||||
i.add<ClockOutUseCase>(ClockOutUseCase.new);
|
||||
i.add<GetActivityLogUseCase>(GetActivityLogUseCase.new);
|
||||
|
||||
// BLoC
|
||||
i.add<ClockInBloc>(ClockInBloc.new);
|
||||
|
||||
@@ -32,28 +32,40 @@ class HomeRepositoryImpl implements HomeRepository {
|
||||
|
||||
Future<List<Shift>> _getShiftsForDate(DateTime date) async {
|
||||
try {
|
||||
final staffId = _currentStaffId;
|
||||
|
||||
// Create start and end timestamps for the target date
|
||||
final DateTime start = DateTime(date.year, date.month, date.day);
|
||||
final DateTime end = DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
|
||||
|
||||
final response = await ExampleConnector.instance
|
||||
.getApplicationsByStaffId(staffId: _currentStaffId)
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.dayStart(_toTimestamp(start))
|
||||
.dayEnd(_toTimestamp(end))
|
||||
.execute();
|
||||
|
||||
final targetYmd = DateFormat('yyyy-MM-dd').format(date);
|
||||
|
||||
return response.data.applications
|
||||
.where((app) {
|
||||
final shiftDate = app.shift.date?.toDate();
|
||||
if (shiftDate == null) return false;
|
||||
|
||||
final isDateMatch = DateFormat('yyyy-MM-dd').format(shiftDate) == targetYmd;
|
||||
final isAssigned = app.status is Known && (app.status as Known).value == ApplicationStatus.ACCEPTED;
|
||||
|
||||
return isDateMatch && isAssigned;
|
||||
})
|
||||
.map((app) => _mapApplicationToShift(app))
|
||||
.toList();
|
||||
// Filter for ACCEPTED applications (same logic as shifts_repository_impl)
|
||||
final apps = response.data.applications.where(
|
||||
(app) => (app.status is Known && (app.status as Known).value == ApplicationStatus.ACCEPTED) || (app.status is Known && (app.status as Known).value == ApplicationStatus.CONFIRMED)
|
||||
);
|
||||
|
||||
final List<Shift> shifts = [];
|
||||
for (final app in apps) {
|
||||
shifts.add(_mapApplicationToShift(app));
|
||||
}
|
||||
|
||||
return shifts;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Timestamp _toTimestamp(DateTime dateTime) {
|
||||
final DateTime utc = dateTime.toUtc();
|
||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
return Timestamp(nanoseconds, seconds);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getRecommendedShifts() async {
|
||||
@@ -93,21 +105,26 @@ class HomeRepositoryImpl implements HomeRepository {
|
||||
final s = app.shift;
|
||||
final r = app.shiftRole;
|
||||
|
||||
return Shift(
|
||||
id: s.id,
|
||||
title: r.role.name,
|
||||
clientName: s.order.business.businessName,
|
||||
hourlyRate: r.role.costPerHour,
|
||||
location: s.location ?? 'Unknown',
|
||||
locationAddress: s.location ?? '',
|
||||
date: s.date?.toDate().toIso8601String() ?? '',
|
||||
startTime: DateFormat('HH:mm').format(r.startTime?.toDate() ?? DateTime.now()),
|
||||
endTime: DateFormat('HH:mm').format(r.endTime?.toDate() ?? DateTime.now()),
|
||||
createdDate: app.createdAt?.toDate().toIso8601String() ?? '',
|
||||
tipsAvailable: false, // Not in API
|
||||
mealProvided: false, // Not in API
|
||||
managers: [], // Not in this query
|
||||
description: null,
|
||||
return ShiftAdapter.fromApplicationData(
|
||||
shiftId: s.id,
|
||||
roleId: r.roleId,
|
||||
roleName: r.role.name,
|
||||
businessName: s.order.business.businessName,
|
||||
companyLogoUrl: s.order.business.companyLogoUrl,
|
||||
costPerHour: r.role.costPerHour,
|
||||
shiftLocation: s.location,
|
||||
teamHubName: s.order.teamHub.hubName,
|
||||
shiftDate: s.date?.toDate(),
|
||||
startTime: r.startTime?.toDate(),
|
||||
endTime: r.endTime?.toDate(),
|
||||
createdAt: app.createdAt?.toDate(),
|
||||
status: 'confirmed',
|
||||
description: s.description,
|
||||
durationDays: s.durationDays,
|
||||
count: r.count,
|
||||
assigned: r.assigned,
|
||||
eventName: s.order.eventName,
|
||||
hasApplied: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:staff_home/src/presentation/widgets/home_page/quick_action_item.
|
||||
import 'package:staff_home/src/presentation/widgets/home_page/recommended_shift_card.dart';
|
||||
import 'package:staff_home/src/presentation/widgets/home_page/section_header.dart';
|
||||
import 'package:staff_home/src/presentation/widgets/shift_card.dart';
|
||||
import 'package:staff_home/src/presentation/widgets/worker/auto_match_toggle.dart';
|
||||
|
||||
/// The home page for the staff worker application.
|
||||
///
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../navigation/home_navigator.dart';
|
||||
|
||||
class ShiftCard extends StatefulWidget {
|
||||
final Shift shift;
|
||||
@@ -73,10 +74,7 @@ class _ShiftCardState extends State<ShiftCard> {
|
||||
? null
|
||||
: () {
|
||||
setState(() => isExpanded = !isExpanded);
|
||||
Modular.to.pushNamed(
|
||||
'/shift-details/${widget.shift.id}',
|
||||
arguments: widget.shift,
|
||||
);
|
||||
Modular.to.pushShiftDetails(widget.shift);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_data_connect/src/session/staff_session_store.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../domain/repositories/payments_repository.dart';
|
||||
|
||||
class PaymentsRepositoryImpl implements PaymentsRepository {
|
||||
@@ -46,25 +47,34 @@ class PaymentsRepositoryImpl implements PaymentsRepository {
|
||||
/// Helper to convert Data Connect Timestamp to DateTime
|
||||
DateTime? _toDateTime(dynamic t) {
|
||||
if (t == null) return null;
|
||||
if (t is DateTime) return t;
|
||||
if (t is String) return DateTime.tryParse(t);
|
||||
DateTime? dt;
|
||||
if (t is DateTime) {
|
||||
dt = t;
|
||||
} else if (t is String) {
|
||||
dt = DateTime.tryParse(t);
|
||||
} else {
|
||||
try {
|
||||
if (t is Timestamp) {
|
||||
dt = t.toDateTime();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
if (t is Timestamp) {
|
||||
return t.toDateTime();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
if (t.runtimeType.toString().contains('Timestamp')) {
|
||||
return (t as dynamic).toDate();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
return DateTime.tryParse(t.toString());
|
||||
} catch (_) {}
|
||||
try {
|
||||
if (dt == null && t.runtimeType.toString().contains('Timestamp')) {
|
||||
dt = (t as dynamic).toDate();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
if (dt == null) {
|
||||
dt = DateTime.tryParse(t.toString());
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (dt != null) {
|
||||
return DateTimeUtils.toDeviceTime(dt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../../domain/repositories/certificates_repository.dart';
|
||||
|
||||
@@ -63,7 +64,9 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
||||
description: null, // Description not available in this query response
|
||||
status: _mapStatus(doc.status),
|
||||
documentUrl: doc.documentUrl,
|
||||
expiryDate: doc.expiryDate?.toDateTime(),
|
||||
expiryDate: doc.expiryDate == null
|
||||
? null
|
||||
: DateTimeUtils.toDeviceTime(doc.expiryDate!.toDateTime()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../../domain/repositories/documents_repository.dart';
|
||||
|
||||
@@ -75,7 +76,9 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
|
||||
description: null, // Description not available in data source
|
||||
status: _mapStatus(doc.status),
|
||||
documentUrl: doc.documentUrl,
|
||||
expiryDate: doc.expiryDate?.toDateTime(),
|
||||
expiryDate: doc.expiryDate == null
|
||||
? null
|
||||
: DateTimeUtils.toDeviceTime(doc.expiryDate!.toDateTime()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -62,14 +63,17 @@ class TaxFormMapper {
|
||||
status: form.status.stringValue,
|
||||
staffId: form.staffId,
|
||||
formData: formData,
|
||||
updatedAt: form.updatedAt?.toDateTime(),
|
||||
updatedAt: form.updatedAt == null
|
||||
? null
|
||||
: DateTimeUtils.toDeviceTime(form.updatedAt!.toDateTime()),
|
||||
);
|
||||
}
|
||||
|
||||
static String? _formatDate(Timestamp? timestamp) {
|
||||
if (timestamp == null) return null;
|
||||
|
||||
final DateTime date = timestamp.toDateTime();
|
||||
final DateTime date =
|
||||
DateTimeUtils.toDeviceTime(timestamp.toDateTime());
|
||||
|
||||
return '${date.month.toString().padLeft(2, '0')}/${date.day.toString().padLeft(2, '0')}/${date.year}';
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../domain/repositories/time_card_repository.dart';
|
||||
|
||||
/// Implementation of [TimeCardRepository] using Firebase Data Connect.
|
||||
@@ -40,12 +41,15 @@ class TimeCardRepositoryImpl implements TimeCardRepository {
|
||||
|
||||
return result.data.applications
|
||||
.where((dc.GetApplicationsByStaffIdApplications app) {
|
||||
final DateTime? shiftDate = app.shift.date?.toDateTime();
|
||||
final DateTime? shiftDate = app.shift.date == null
|
||||
? null
|
||||
: DateTimeUtils.toDeviceTime(app.shift.date!.toDateTime());
|
||||
if (shiftDate == null) return false;
|
||||
return shiftDate.year == month.year && shiftDate.month == month.month;
|
||||
})
|
||||
.map((dc.GetApplicationsByStaffIdApplications app) {
|
||||
final DateTime shiftDate = app.shift.date!.toDateTime();
|
||||
final DateTime shiftDate =
|
||||
DateTimeUtils.toDeviceTime(app.shift.date!.toDateTime());
|
||||
final String startTime = _formatTime(app.checkInTime) ?? _formatTime(app.shift.startTime) ?? '';
|
||||
final String endTime = _formatTime(app.checkOutTime) ?? _formatTime(app.shift.endTime) ?? '';
|
||||
|
||||
@@ -73,6 +77,7 @@ class TimeCardRepositoryImpl implements TimeCardRepository {
|
||||
|
||||
String? _formatTime(fdc.Timestamp? timestamp) {
|
||||
if (timestamp == null) return null;
|
||||
return DateFormat('HH:mm').format(timestamp.toDateTime());
|
||||
return DateFormat('HH:mm')
|
||||
.format(DateTimeUtils.toDeviceTime(timestamp.toDateTime()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
||||
final response = await _dataConnect
|
||||
.getStaffByUserId(userId: user.uid)
|
||||
.execute();
|
||||
if (response.data.staffs.isNotEmpty) {
|
||||
_cachedStaffId = response.data.staffs.first.id;
|
||||
return _cachedStaffId!;
|
||||
@@ -47,9 +49,9 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
}
|
||||
|
||||
// 4. Fallback (should ideally not happen if DB is seeded)
|
||||
return user.uid;
|
||||
return user.uid;
|
||||
}
|
||||
|
||||
|
||||
DateTime? _toDateTime(dynamic t) {
|
||||
if (t == null) return null;
|
||||
DateTime? dt;
|
||||
@@ -75,62 +77,76 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getMyShifts() async {
|
||||
return _fetchApplications(dc.ApplicationStatus.ACCEPTED);
|
||||
/// Helper method to map Data Connect application to domain Shift using ShiftAdapter.
|
||||
Shift _mapApplicationToShift(
|
||||
dynamic app,
|
||||
String status, {
|
||||
bool hasApplied = true,
|
||||
}) {
|
||||
return ShiftAdapter.fromApplicationData(
|
||||
shiftId: app.shift.id,
|
||||
roleId: app.shiftRole.roleId,
|
||||
roleName: app.shiftRole.role.name,
|
||||
businessName: app.shift.order.business.businessName,
|
||||
companyLogoUrl: app.shift.order.business.companyLogoUrl,
|
||||
costPerHour: app.shiftRole.role.costPerHour,
|
||||
shiftLocation: app.shift.location,
|
||||
teamHubName: app.shift.order.teamHub.hubName,
|
||||
shiftDate: _toDateTime(app.shift.date),
|
||||
startTime: _toDateTime(app.shiftRole.startTime),
|
||||
endTime: _toDateTime(app.shiftRole.endTime),
|
||||
createdAt: _toDateTime(app.createdAt),
|
||||
status: status,
|
||||
description: app.shift.description,
|
||||
durationDays: app.shift.durationDays,
|
||||
count: app.shiftRole.count,
|
||||
assigned: app.shiftRole.assigned,
|
||||
eventName: app.shift.order.eventName,
|
||||
hasApplied: hasApplied,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getMyShifts({
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
}) async {
|
||||
return _fetchApplications(
|
||||
[dc.ApplicationStatus.ACCEPTED, dc.ApplicationStatus.CONFIRMED],
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getPendingAssignments() async {
|
||||
return _fetchApplications(dc.ApplicationStatus.PENDING);
|
||||
return _fetchApplications([dc.ApplicationStatus.PENDING]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getCancelledShifts() async {
|
||||
return _fetchApplications(dc.ApplicationStatus.REJECTED);
|
||||
return _fetchApplications([dc.ApplicationStatus.REJECTED]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getHistoryShifts() async {
|
||||
return _fetchApplications(dc.ApplicationStatus.CHECKED_OUT);
|
||||
}
|
||||
|
||||
Future<List<Shift>> _fetchApplications(dc.ApplicationStatus status) async {
|
||||
try {
|
||||
final staffId = await _getStaffId();
|
||||
final response = await _dataConnect
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.listCompletedApplicationsByStaffId(staffId: staffId)
|
||||
.execute();
|
||||
|
||||
final apps = response.data.applications.where((app) => app.status.stringValue == status.name);
|
||||
final List<Shift> shifts = [];
|
||||
|
||||
for (final app in apps) {
|
||||
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||
|
||||
final shift = await _getShiftDetails(app.shift.id);
|
||||
if (shift != null) {
|
||||
// Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED)
|
||||
shifts.add(Shift(
|
||||
id: shift.id,
|
||||
title: shift.title,
|
||||
clientName: shift.clientName,
|
||||
logoUrl: shift.logoUrl,
|
||||
hourlyRate: shift.hourlyRate,
|
||||
location: shift.location,
|
||||
locationAddress: shift.locationAddress,
|
||||
date: shift.date,
|
||||
startTime: shift.startTime,
|
||||
endTime: shift.endTime,
|
||||
createdDate: shift.createdDate,
|
||||
status: _mapStatus(status),
|
||||
description: shift.description,
|
||||
durationDays: shift.durationDays,
|
||||
requiredSlots: shift.requiredSlots,
|
||||
filledSlots: shift.filledSlots,
|
||||
));
|
||||
}
|
||||
for (final app in response.data.applications) {
|
||||
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||
|
||||
shifts.add(
|
||||
_mapApplicationToShift(
|
||||
app,
|
||||
_mapStatus(dc.ApplicationStatus.CHECKED_OUT),
|
||||
),
|
||||
);
|
||||
}
|
||||
return shifts;
|
||||
} catch (e) {
|
||||
@@ -138,6 +154,51 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Shift>> _fetchApplications(
|
||||
List<dc.ApplicationStatus> statuses, {
|
||||
DateTime? start,
|
||||
DateTime? end,
|
||||
}) async {
|
||||
try {
|
||||
final staffId = await _getStaffId();
|
||||
var query = _dataConnect.getApplicationsByStaffId(staffId: staffId);
|
||||
if (start != null && end != null) {
|
||||
query = query
|
||||
.dayStart(_toTimestamp(start))
|
||||
.dayEnd(_toTimestamp(end));
|
||||
}
|
||||
final response = await query.execute();
|
||||
|
||||
final statusNames = statuses.map((s) => s.name).toSet();
|
||||
final apps = response.data.applications.where(
|
||||
(app) => statusNames.contains(app.status.stringValue),
|
||||
);
|
||||
final List<Shift> shifts = [];
|
||||
|
||||
for (final app in apps) {
|
||||
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||
|
||||
// Use the first matching status for mapping
|
||||
final matchingStatus = statuses.firstWhere(
|
||||
(s) => s.name == app.status.stringValue,
|
||||
orElse: () => statuses.first,
|
||||
);
|
||||
shifts.add(_mapApplicationToShift(app, _mapStatus(matchingStatus)));
|
||||
}
|
||||
return shifts;
|
||||
} catch (e) {
|
||||
return <Shift>[];
|
||||
}
|
||||
}
|
||||
|
||||
Timestamp _toTimestamp(DateTime dateTime) {
|
||||
final DateTime utc = dateTime.toUtc();
|
||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||
return Timestamp(nanoseconds, seconds);
|
||||
}
|
||||
|
||||
String _mapStatus(dc.ApplicationStatus status) {
|
||||
switch (status) {
|
||||
case dc.ApplicationStatus.ACCEPTED:
|
||||
@@ -156,133 +217,301 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getAvailableShifts(String query, String type) async {
|
||||
try {
|
||||
final result = await _dataConnect.listShifts().execute();
|
||||
final allShifts = result.data.shifts;
|
||||
|
||||
final List<Shift> mappedShifts = [];
|
||||
|
||||
for (final s in allShifts) {
|
||||
// For each shift, map to Domain Shift
|
||||
// Note: date fields in generated code might be specific types
|
||||
final startDt = _toDateTime(s.startTime);
|
||||
final endDt = _toDateTime(s.endTime);
|
||||
final createdDt = _toDateTime(s.createdAt);
|
||||
|
||||
mappedShifts.add(Shift(
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
clientName: s.order.business.businessName,
|
||||
logoUrl: null,
|
||||
hourlyRate: s.cost ?? 0.0,
|
||||
location: s.location ?? '',
|
||||
locationAddress: s.locationAddress ?? '',
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: s.status?.stringValue.toLowerCase() ?? 'open',
|
||||
description: s.description,
|
||||
durationDays: s.durationDays,
|
||||
requiredSlots: null, // Basic list doesn't fetch detailed role stats yet
|
||||
filledSlots: null,
|
||||
));
|
||||
}
|
||||
|
||||
if (query.isNotEmpty) {
|
||||
return mappedShifts.where((s) =>
|
||||
s.title.toLowerCase().contains(query.toLowerCase()) ||
|
||||
s.clientName.toLowerCase().contains(query.toLowerCase())
|
||||
).toList();
|
||||
}
|
||||
|
||||
return mappedShifts;
|
||||
|
||||
} catch (e) {
|
||||
return <Shift>[];
|
||||
try {
|
||||
final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId;
|
||||
if (vendorId == null || vendorId.isEmpty) {
|
||||
return <Shift>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Shift?> getShiftDetails(String shiftId) async {
|
||||
return _getShiftDetails(shiftId);
|
||||
}
|
||||
|
||||
Future<Shift?> _getShiftDetails(String shiftId) async {
|
||||
try {
|
||||
final result = await _dataConnect.getShiftById(id: shiftId).execute();
|
||||
final s = result.data.shift;
|
||||
if (s == null) return null;
|
||||
|
||||
int? required;
|
||||
int? filled;
|
||||
try {
|
||||
final rolesRes = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
|
||||
if (rolesRes.data.shiftRoles.isNotEmpty) {
|
||||
required = 0;
|
||||
filled = 0;
|
||||
for(var r in rolesRes.data.shiftRoles) {
|
||||
required = (required ?? 0) + r.count;
|
||||
filled = (filled ?? 0) + (r.assigned ?? 0);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
final result = await _dataConnect
|
||||
.listShiftRolesByVendorId(vendorId: vendorId)
|
||||
.execute();
|
||||
final allShiftRoles = result.data.shiftRoles;
|
||||
|
||||
final startDt = _toDateTime(s.startTime);
|
||||
final endDt = _toDateTime(s.endTime);
|
||||
final createdDt = _toDateTime(s.createdAt);
|
||||
|
||||
return Shift(
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
clientName: s.order.business.businessName,
|
||||
final List<Shift> mappedShifts = [];
|
||||
for (final sr in allShiftRoles) {
|
||||
final DateTime? shiftDate = _toDateTime(sr.shift.date);
|
||||
final startDt = _toDateTime(sr.startTime);
|
||||
final endDt = _toDateTime(sr.endTime);
|
||||
final createdDt = _toDateTime(sr.createdAt);
|
||||
mappedShifts.add(
|
||||
Shift(
|
||||
id: sr.shiftId,
|
||||
roleId: sr.roleId,
|
||||
title: sr.role.name,
|
||||
clientName: sr.shift.order.business.businessName,
|
||||
logoUrl: null,
|
||||
hourlyRate: s.cost ?? 0.0,
|
||||
location: s.location ?? '',
|
||||
locationAddress: s.locationAddress ?? '',
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||
hourlyRate: sr.role.costPerHour,
|
||||
location: sr.shift.location ?? '',
|
||||
locationAddress: sr.shift.locationAddress ?? '',
|
||||
date: shiftDate?.toIso8601String() ?? '',
|
||||
startTime: startDt != null
|
||||
? DateFormat('HH:mm').format(startDt)
|
||||
: '',
|
||||
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: s.status?.stringValue ?? 'OPEN',
|
||||
description: s.description,
|
||||
durationDays: s.durationDays,
|
||||
requiredSlots: required,
|
||||
filledSlots: filled,
|
||||
status: sr.shift.status?.stringValue.toLowerCase() ?? 'open',
|
||||
description: sr.shift.description,
|
||||
durationDays: sr.shift.durationDays,
|
||||
requiredSlots: sr.count,
|
||||
filledSlots: sr.assigned ?? 0,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (query.isNotEmpty) {
|
||||
return mappedShifts
|
||||
.where(
|
||||
(s) =>
|
||||
s.title.toLowerCase().contains(query.toLowerCase()) ||
|
||||
s.clientName.toLowerCase().contains(query.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
return mappedShifts;
|
||||
} catch (e) {
|
||||
return <Shift>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> applyForShift(String shiftId, {bool isInstantBook = false}) async {
|
||||
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
|
||||
if (rolesResult.data.shiftRoles.isEmpty) throw Exception('No open roles for this shift');
|
||||
|
||||
final role = rolesResult.data.shiftRoles.first;
|
||||
|
||||
final staffId = await _getStaffId();
|
||||
await _dataConnect.createApplication(
|
||||
shiftId: shiftId,
|
||||
staffId: staffId,
|
||||
roleId: role.roleId,
|
||||
status: isInstantBook ? dc.ApplicationStatus.ACCEPTED : dc.ApplicationStatus.PENDING,
|
||||
origin: dc.ApplicationOrigin.STAFF,
|
||||
).execute();
|
||||
Future<Shift?> getShiftDetails(String shiftId, {String? roleId}) async {
|
||||
return _getShiftDetails(shiftId, roleId: roleId);
|
||||
}
|
||||
|
||||
Future<Shift?> _getShiftDetails(String shiftId, {String? roleId}) async {
|
||||
try {
|
||||
if (roleId != null && roleId.isNotEmpty) {
|
||||
final roleResult = await _dataConnect
|
||||
.getShiftRoleById(shiftId: shiftId, roleId: roleId)
|
||||
.execute();
|
||||
final sr = roleResult.data.shiftRole;
|
||||
if (sr == null) return null;
|
||||
|
||||
final DateTime? startDt = _toDateTime(sr.startTime);
|
||||
final DateTime? endDt = _toDateTime(sr.endTime);
|
||||
final DateTime? createdDt = _toDateTime(sr.createdAt);
|
||||
|
||||
final String? staffId = _auth.currentUser?.uid;
|
||||
bool hasApplied = false;
|
||||
String status = 'open';
|
||||
if (staffId != null) {
|
||||
final apps = await _dataConnect
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.execute();
|
||||
final app = apps.data.applications
|
||||
.where(
|
||||
(a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId,
|
||||
)
|
||||
.firstOrNull;
|
||||
if (app != null) {
|
||||
hasApplied = true;
|
||||
if (app.status is dc.Known<dc.ApplicationStatus>) {
|
||||
final dc.ApplicationStatus s =
|
||||
(app.status as dc.Known<dc.ApplicationStatus>).value;
|
||||
status = _mapStatus(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Shift(
|
||||
id: sr.shiftId,
|
||||
roleId: sr.roleId,
|
||||
title: sr.shift.order.business.businessName,
|
||||
clientName: sr.shift.order.business.businessName,
|
||||
logoUrl: sr.shift.order.business.companyLogoUrl,
|
||||
hourlyRate: sr.role.costPerHour,
|
||||
location: sr.shift.location ?? sr.shift.order.teamHub.hubName,
|
||||
locationAddress: sr.shift.locationAddress ?? '',
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: status,
|
||||
description: sr.shift.description,
|
||||
durationDays: null,
|
||||
requiredSlots: sr.count,
|
||||
filledSlots: sr.assigned ?? 0,
|
||||
hasApplied: hasApplied,
|
||||
totalValue: sr.totalValue,
|
||||
);
|
||||
}
|
||||
|
||||
final result = await _dataConnect.getShiftById(id: shiftId).execute();
|
||||
final s = result.data.shift;
|
||||
if (s == null) return null;
|
||||
|
||||
int? required;
|
||||
int? filled;
|
||||
try {
|
||||
final rolesRes = await _dataConnect
|
||||
.listShiftRolesByShiftId(shiftId: shiftId)
|
||||
.execute();
|
||||
if (rolesRes.data.shiftRoles.isNotEmpty) {
|
||||
required = 0;
|
||||
filled = 0;
|
||||
for (var r in rolesRes.data.shiftRoles) {
|
||||
required = (required ?? 0) + r.count;
|
||||
filled = (filled ?? 0) + (r.assigned ?? 0);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
final startDt = _toDateTime(s.startTime);
|
||||
final endDt = _toDateTime(s.endTime);
|
||||
final createdDt = _toDateTime(s.createdAt);
|
||||
|
||||
return Shift(
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
clientName: s.order.business.businessName,
|
||||
logoUrl: null,
|
||||
hourlyRate: s.cost ?? 0.0,
|
||||
location: s.location ?? '',
|
||||
locationAddress: s.locationAddress ?? '',
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: s.status?.stringValue ?? 'OPEN',
|
||||
description: s.description,
|
||||
durationDays: s.durationDays,
|
||||
requiredSlots: required,
|
||||
filledSlots: filled,
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> applyForShift(
|
||||
String shiftId, {
|
||||
bool isInstantBook = false,
|
||||
String? roleId,
|
||||
}) async {
|
||||
final staffId = await _getStaffId();
|
||||
|
||||
String targetRoleId = roleId ?? '';
|
||||
if (targetRoleId.isEmpty) {
|
||||
throw Exception('Missing role id.');
|
||||
}
|
||||
|
||||
final roleResult = await _dataConnect
|
||||
.getShiftRoleById(shiftId: shiftId, roleId: targetRoleId)
|
||||
.execute();
|
||||
final role = roleResult.data.shiftRole;
|
||||
if (role == null) {
|
||||
throw Exception('Shift role not found');
|
||||
}
|
||||
final shiftResult = await _dataConnect.getShiftById(id: shiftId).execute();
|
||||
final shift = shiftResult.data.shift;
|
||||
if (shift == null) {
|
||||
throw Exception('Shift not found');
|
||||
}
|
||||
final DateTime? shiftDate = _toDateTime(shift.date);
|
||||
if (shiftDate != null) {
|
||||
final DateTime dayStartUtc = DateTime.utc(
|
||||
shiftDate.year,
|
||||
shiftDate.month,
|
||||
shiftDate.day,
|
||||
);
|
||||
final DateTime dayEndUtc = DateTime.utc(
|
||||
shiftDate.year,
|
||||
shiftDate.month,
|
||||
shiftDate.day,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
999,
|
||||
999,
|
||||
);
|
||||
print(
|
||||
'Staff applyForShift: dayStartUtc=${_toTimestamp(dayStartUtc).toJson()} '
|
||||
'dayEndUtc=${_toTimestamp(dayEndUtc).toJson()}',
|
||||
);
|
||||
final dayApplications = await _dataConnect
|
||||
.vaidateDayStaffApplication(staffId: staffId)
|
||||
.dayStart(_toTimestamp(dayStartUtc))
|
||||
.dayEnd(_toTimestamp(dayEndUtc))
|
||||
.execute();
|
||||
if (dayApplications.data.applications.isNotEmpty) {
|
||||
throw Exception('The user already has a shift that day.');
|
||||
}
|
||||
}
|
||||
final existingApplicationResult = await _dataConnect
|
||||
.getApplicationByStaffShiftAndRole(
|
||||
staffId: staffId,
|
||||
shiftId: shiftId,
|
||||
roleId: targetRoleId,
|
||||
)
|
||||
.execute();
|
||||
if (existingApplicationResult.data.applications.isNotEmpty) {
|
||||
throw Exception('Application already exists.');
|
||||
}
|
||||
final int assigned = role.assigned ?? 0;
|
||||
if (assigned >= role.count) {
|
||||
throw Exception('This shift is full.');
|
||||
}
|
||||
|
||||
final int filled = shift.filled ?? 0;
|
||||
|
||||
String? appId;
|
||||
bool updatedRole = false;
|
||||
bool updatedShift = false;
|
||||
try {
|
||||
final appResult = await _dataConnect
|
||||
.createApplication(
|
||||
shiftId: shiftId,
|
||||
staffId: staffId,
|
||||
roleId: targetRoleId,
|
||||
status: dc.ApplicationStatus.CONFIRMED,
|
||||
origin: dc.ApplicationOrigin.STAFF,
|
||||
)
|
||||
// TODO: this should be PENDING so a vendor can accept it.
|
||||
.execute();
|
||||
appId = appResult.data.application_insert.id;
|
||||
|
||||
await _dataConnect
|
||||
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
|
||||
.assigned(assigned + 1)
|
||||
.execute();
|
||||
updatedRole = true;
|
||||
|
||||
await _dataConnect.updateShift(id: shiftId).filled(filled + 1).execute();
|
||||
updatedShift = true;
|
||||
} catch (e) {
|
||||
if (updatedShift) {
|
||||
await _dataConnect.updateShift(id: shiftId).filled(filled).execute();
|
||||
}
|
||||
if (updatedRole) {
|
||||
await _dataConnect
|
||||
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
|
||||
.assigned(assigned)
|
||||
.execute();
|
||||
}
|
||||
if (appId != null) {
|
||||
await _dataConnect.deleteApplication(id: appId).execute();
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> acceptShift(String shiftId) async {
|
||||
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> declineShift(String shiftId) async {
|
||||
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.REJECTED);
|
||||
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.CONFIRMED);
|
||||
}
|
||||
|
||||
Future<void> _updateApplicationStatus(String shiftId, dc.ApplicationStatus newStatus) async {
|
||||
@override
|
||||
Future<void> declineShift(String shiftId) async {
|
||||
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.REJECTED);
|
||||
}
|
||||
|
||||
Future<void> _updateApplicationStatus(
|
||||
String shiftId,
|
||||
dc.ApplicationStatus newStatus,
|
||||
) async {
|
||||
String? appId = _shiftToAppIdMap[shiftId];
|
||||
String? roleId;
|
||||
|
||||
@@ -293,44 +522,49 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
// Re-check map
|
||||
appId = _shiftToAppIdMap[shiftId];
|
||||
if (appId != null) {
|
||||
roleId = _appToRoleIdMap[appId];
|
||||
roleId = _appToRoleIdMap[appId];
|
||||
} else {
|
||||
// Fallback fetch
|
||||
final staffId = await _getStaffId();
|
||||
final apps = await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute();
|
||||
final app = apps.data.applications.where((a) => a.shiftId == shiftId).firstOrNull;
|
||||
if (app != null) {
|
||||
appId = app.id;
|
||||
roleId = app.shiftRole.id;
|
||||
}
|
||||
// Fallback fetch
|
||||
final staffId = await _getStaffId();
|
||||
final apps = await _dataConnect
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.execute();
|
||||
final app = apps.data.applications
|
||||
.where((a) => a.shiftId == shiftId)
|
||||
.firstOrNull;
|
||||
if (app != null) {
|
||||
appId = app.id;
|
||||
roleId = app.shiftRole.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (appId == null || roleId == null) {
|
||||
// If we are rejecting and can't find an application, create one as rejected (declining an available shift)
|
||||
if (newStatus == dc.ApplicationStatus.REJECTED) {
|
||||
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
|
||||
if (rolesResult.data.shiftRoles.isNotEmpty) {
|
||||
final role = rolesResult.data.shiftRoles.first;
|
||||
final staffId = await _getStaffId();
|
||||
await _dataConnect.createApplication(
|
||||
final rolesResult = await _dataConnect
|
||||
.listShiftRolesByShiftId(shiftId: shiftId)
|
||||
.execute();
|
||||
if (rolesResult.data.shiftRoles.isNotEmpty) {
|
||||
final role = rolesResult.data.shiftRoles.first;
|
||||
final staffId = await _getStaffId();
|
||||
await _dataConnect
|
||||
.createApplication(
|
||||
shiftId: shiftId,
|
||||
staffId: staffId,
|
||||
roleId: role.id,
|
||||
status: dc.ApplicationStatus.REJECTED,
|
||||
origin: dc.ApplicationOrigin.STAFF,
|
||||
).execute();
|
||||
return;
|
||||
}
|
||||
)
|
||||
.execute();
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw Exception("Application not found for shift $shiftId");
|
||||
}
|
||||
|
||||
await _dataConnect.updateApplicationStatus(
|
||||
id: appId,
|
||||
roleId: roleId,
|
||||
)
|
||||
.status(newStatus)
|
||||
.execute();
|
||||
await _dataConnect
|
||||
.updateApplicationStatus(id: appId, roleId: roleId)
|
||||
.status(newStatus)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
class GetMyShiftsArguments extends UseCaseArgument {
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
const GetMyShiftsArguments({
|
||||
required this.start,
|
||||
required this.end,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class GetShiftDetailsArguments extends Equatable {
|
||||
final String shiftId;
|
||||
final String? roleId;
|
||||
|
||||
const GetShiftDetailsArguments({
|
||||
required this.shiftId,
|
||||
this.roleId,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [shiftId, roleId];
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
/// Implementations of this interface should reside in the data layer.
|
||||
abstract interface class ShiftsRepositoryInterface {
|
||||
/// Retrieves the list of shifts assigned to the current user.
|
||||
Future<List<Shift>> getMyShifts();
|
||||
Future<List<Shift>> getMyShifts({
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
});
|
||||
|
||||
/// Retrieves available shifts matching the given [query] and [type].
|
||||
Future<List<Shift>> getAvailableShifts(String query, String type);
|
||||
@@ -15,12 +18,16 @@ abstract interface class ShiftsRepositoryInterface {
|
||||
Future<List<Shift>> getPendingAssignments();
|
||||
|
||||
/// Retrieves detailed information for a specific shift by [shiftId].
|
||||
Future<Shift?> getShiftDetails(String shiftId);
|
||||
Future<Shift?> getShiftDetails(String shiftId, {String? roleId});
|
||||
|
||||
/// Applies for a specific open shift.
|
||||
///
|
||||
/// [isInstantBook] determines if the application should be immediately accepted.
|
||||
Future<void> applyForShift(String shiftId, {bool isInstantBook = false});
|
||||
Future<void> applyForShift(
|
||||
String shiftId, {
|
||||
bool isInstantBook = false,
|
||||
String? roleId,
|
||||
});
|
||||
|
||||
/// Accepts a pending shift assignment.
|
||||
Future<void> acceptShift(String shiftId);
|
||||
|
||||
@@ -5,7 +5,15 @@ class ApplyForShiftUseCase {
|
||||
|
||||
ApplyForShiftUseCase(this.repository);
|
||||
|
||||
Future<void> call(String shiftId, {bool isInstantBook = false}) async {
|
||||
return repository.applyForShift(shiftId, isInstantBook: isInstantBook);
|
||||
Future<void> call(
|
||||
String shiftId, {
|
||||
bool isInstantBook = false,
|
||||
String? roleId,
|
||||
}) async {
|
||||
return repository.applyForShift(
|
||||
shiftId,
|
||||
isInstantBook: isInstantBook,
|
||||
roleId: roleId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../arguments/get_my_shifts_arguments.dart';
|
||||
import '../repositories/shifts_repository_interface.dart';
|
||||
|
||||
/// Use case for retrieving the user's assigned shifts.
|
||||
///
|
||||
/// This use case delegates to [ShiftsRepositoryInterface].
|
||||
class GetMyShiftsUseCase extends NoInputUseCase<List<Shift>> {
|
||||
class GetMyShiftsUseCase extends UseCase<GetMyShiftsArguments, List<Shift>> {
|
||||
final ShiftsRepositoryInterface repository;
|
||||
|
||||
GetMyShiftsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<List<Shift>> call() async {
|
||||
return repository.getMyShifts();
|
||||
Future<List<Shift>> call(GetMyShiftsArguments arguments) async {
|
||||
return repository.getMyShifts(
|
||||
start: arguments.start,
|
||||
end: arguments.end,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../arguments/get_shift_details_arguments.dart';
|
||||
import '../repositories/shifts_repository_interface.dart';
|
||||
|
||||
class GetShiftDetailsUseCase extends UseCase<String, Shift?> {
|
||||
class GetShiftDetailsUseCase extends UseCase<GetShiftDetailsArguments, Shift?> {
|
||||
final ShiftsRepositoryInterface repository;
|
||||
|
||||
GetShiftDetailsUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Shift?> call(String params) {
|
||||
return repository.getShiftDetails(params);
|
||||
Future<Shift?> call(GetShiftDetailsArguments params) {
|
||||
return repository.getShiftDetails(
|
||||
params.shiftId,
|
||||
roleId: params.roleId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
|
||||
import '../../../domain/usecases/apply_for_shift_usecase.dart';
|
||||
import '../../../domain/usecases/decline_shift_usecase.dart';
|
||||
import '../../../domain/usecases/get_shift_details_usecase.dart';
|
||||
import '../../../domain/arguments/get_shift_details_arguments.dart';
|
||||
import 'shift_details_event.dart';
|
||||
import 'shift_details_state.dart';
|
||||
|
||||
@@ -26,7 +27,9 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
) async {
|
||||
emit(ShiftDetailsLoading());
|
||||
try {
|
||||
final shift = await getShiftDetails(event.shiftId);
|
||||
final shift = await getShiftDetails(
|
||||
GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
|
||||
);
|
||||
if (shift != null) {
|
||||
emit(ShiftDetailsLoaded(shift));
|
||||
} else {
|
||||
@@ -42,8 +45,14 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||
Emitter<ShiftDetailsState> emit,
|
||||
) async {
|
||||
try {
|
||||
await applyForShift(event.shiftId, isInstantBook: true);
|
||||
emit(const ShiftActionSuccess("Shift successfully booked!"));
|
||||
await applyForShift(
|
||||
event.shiftId,
|
||||
isInstantBook: true,
|
||||
roleId: event.roleId,
|
||||
);
|
||||
emit(
|
||||
ShiftActionSuccess("Shift successfully booked!", shiftDate: event.date),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ShiftDetailsError(e.toString()));
|
||||
}
|
||||
|
||||
@@ -9,18 +9,21 @@ abstract class ShiftDetailsEvent extends Equatable {
|
||||
|
||||
class LoadShiftDetailsEvent extends ShiftDetailsEvent {
|
||||
final String shiftId;
|
||||
const LoadShiftDetailsEvent(this.shiftId);
|
||||
final String? roleId;
|
||||
const LoadShiftDetailsEvent(this.shiftId, {this.roleId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [shiftId];
|
||||
List<Object?> get props => [shiftId, roleId];
|
||||
}
|
||||
|
||||
class BookShiftDetailsEvent extends ShiftDetailsEvent {
|
||||
final String shiftId;
|
||||
const BookShiftDetailsEvent(this.shiftId);
|
||||
final String? roleId;
|
||||
final DateTime? date;
|
||||
const BookShiftDetailsEvent(this.shiftId, {this.roleId, this.date});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [shiftId];
|
||||
List<Object?> get props => [shiftId, roleId, date];
|
||||
}
|
||||
|
||||
class DeclineShiftDetailsEvent extends ShiftDetailsEvent {
|
||||
|
||||
@@ -30,8 +30,9 @@ class ShiftDetailsError extends ShiftDetailsState {
|
||||
|
||||
class ShiftActionSuccess extends ShiftDetailsState {
|
||||
final String message;
|
||||
const ShiftActionSuccess(this.message);
|
||||
final DateTime? shiftDate;
|
||||
const ShiftActionSuccess(this.message, {this.shiftDate});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => [message, shiftDate];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../../domain/arguments/get_available_shifts_arguments.dart';
|
||||
import '../../../domain/arguments/get_my_shifts_arguments.dart';
|
||||
import '../../../domain/usecases/get_available_shifts_usecase.dart';
|
||||
import '../../../domain/usecases/get_cancelled_shifts_usecase.dart';
|
||||
import '../../../domain/usecases/get_history_shifts_usecase.dart';
|
||||
@@ -28,6 +29,8 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
required this.getHistoryShifts,
|
||||
}) : super(ShiftsInitial()) {
|
||||
on<LoadShiftsEvent>(_onLoadShifts);
|
||||
on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
|
||||
on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
|
||||
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
||||
}
|
||||
|
||||
@@ -43,10 +46,12 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
// Or load all for simplicity as per prototype logic which had them all in memory.
|
||||
|
||||
try {
|
||||
final myShiftsResult = await getMyShifts();
|
||||
final List<DateTime> days = _getCalendarDaysForOffset(0);
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: days.first, end: days.last),
|
||||
);
|
||||
final pendingResult = await getPendingAssignments();
|
||||
final cancelledResult = await getCancelledShifts();
|
||||
final historyResult = await getHistoryShifts();
|
||||
|
||||
// Initial available with defaults
|
||||
final availableResult = await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
@@ -56,7 +61,66 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
pendingShifts: pendingResult,
|
||||
cancelledShifts: cancelledResult,
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
historyShifts: const [],
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
} catch (_) {
|
||||
emit(const ShiftsError('Failed to load shifts'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadHistoryShifts(
|
||||
LoadHistoryShiftsEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ShiftsLoaded) return;
|
||||
if (currentState.historyLoading || currentState.historyLoaded) return;
|
||||
|
||||
emit(currentState.copyWith(historyLoading: true));
|
||||
try {
|
||||
final historyResult = await getHistoryShifts();
|
||||
emit(currentState.copyWith(
|
||||
historyShifts: historyResult,
|
||||
historyLoading: false,
|
||||
historyLoaded: true,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(currentState.copyWith(historyLoading: false));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadShiftsForRange(
|
||||
LoadShiftsForRangeEvent event,
|
||||
Emitter<ShiftsState> emit,
|
||||
) async {
|
||||
try {
|
||||
final myShiftsResult = await getMyShifts(
|
||||
GetMyShiftsArguments(start: event.start, end: event.end),
|
||||
);
|
||||
|
||||
if (state is ShiftsLoaded) {
|
||||
final currentState = state as ShiftsLoaded;
|
||||
emit(currentState.copyWith(myShifts: myShiftsResult));
|
||||
return;
|
||||
}
|
||||
|
||||
final pendingResult = await getPendingAssignments();
|
||||
final cancelledResult = await getCancelledShifts();
|
||||
final availableResult =
|
||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||
|
||||
emit(ShiftsLoaded(
|
||||
myShifts: myShiftsResult,
|
||||
pendingShifts: pendingResult,
|
||||
cancelledShifts: cancelledResult,
|
||||
availableShifts: _filterPastShifts(availableResult),
|
||||
historyShifts: const [],
|
||||
historyLoading: false,
|
||||
historyLoaded: false,
|
||||
searchQuery: '',
|
||||
jobType: 'all',
|
||||
));
|
||||
@@ -91,6 +155,17 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
||||
}
|
||||
}
|
||||
|
||||
List<DateTime> _getCalendarDaysForOffset(int weekOffset) {
|
||||
final now = DateTime.now();
|
||||
final int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
|
||||
final int daysSinceFriday = (reactDayIndex + 2) % 7;
|
||||
final start = now
|
||||
.subtract(Duration(days: daysSinceFriday))
|
||||
.add(Duration(days: weekOffset * 7));
|
||||
final startDate = DateTime(start.year, start.month, start.day);
|
||||
return List.generate(7, (index) => startDate.add(Duration(days: index)));
|
||||
}
|
||||
|
||||
List<Shift> _filterPastShifts(List<Shift> shifts) {
|
||||
final now = DateTime.now();
|
||||
return shifts.where((shift) {
|
||||
|
||||
@@ -10,6 +10,21 @@ sealed class ShiftsEvent extends Equatable {
|
||||
|
||||
class LoadShiftsEvent extends ShiftsEvent {}
|
||||
|
||||
class LoadHistoryShiftsEvent extends ShiftsEvent {}
|
||||
|
||||
class LoadShiftsForRangeEvent extends ShiftsEvent {
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
const LoadShiftsForRangeEvent({
|
||||
required this.start,
|
||||
required this.end,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [start, end];
|
||||
}
|
||||
|
||||
class FilterAvailableShiftsEvent extends ShiftsEvent {
|
||||
final String? query;
|
||||
final String? jobType;
|
||||
|
||||
@@ -18,6 +18,8 @@ class ShiftsLoaded extends ShiftsState {
|
||||
final List<Shift> cancelledShifts;
|
||||
final List<Shift> availableShifts;
|
||||
final List<Shift> historyShifts;
|
||||
final bool historyLoading;
|
||||
final bool historyLoaded;
|
||||
final String searchQuery;
|
||||
final String jobType;
|
||||
|
||||
@@ -27,6 +29,8 @@ class ShiftsLoaded extends ShiftsState {
|
||||
required this.cancelledShifts,
|
||||
required this.availableShifts,
|
||||
required this.historyShifts,
|
||||
required this.historyLoading,
|
||||
required this.historyLoaded,
|
||||
required this.searchQuery,
|
||||
required this.jobType,
|
||||
});
|
||||
@@ -37,6 +41,8 @@ class ShiftsLoaded extends ShiftsState {
|
||||
List<Shift>? cancelledShifts,
|
||||
List<Shift>? availableShifts,
|
||||
List<Shift>? historyShifts,
|
||||
bool? historyLoading,
|
||||
bool? historyLoaded,
|
||||
String? searchQuery,
|
||||
String? jobType,
|
||||
}) {
|
||||
@@ -46,6 +52,8 @@ class ShiftsLoaded extends ShiftsState {
|
||||
cancelledShifts: cancelledShifts ?? this.cancelledShifts,
|
||||
availableShifts: availableShifts ?? this.availableShifts,
|
||||
historyShifts: historyShifts ?? this.historyShifts,
|
||||
historyLoading: historyLoading ?? this.historyLoading,
|
||||
historyLoaded: historyLoaded ?? this.historyLoaded,
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
jobType: jobType ?? this.jobType,
|
||||
);
|
||||
@@ -58,6 +66,8 @@ class ShiftsLoaded extends ShiftsState {
|
||||
cancelledShifts,
|
||||
availableShifts,
|
||||
historyShifts,
|
||||
historyLoading,
|
||||
historyLoaded,
|
||||
searchQuery,
|
||||
jobType,
|
||||
];
|
||||
|
||||
@@ -2,7 +2,11 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
extension ShiftsNavigator on IModularNavigator {
|
||||
void navigateToShiftsHome({DateTime? selectedDate}) {
|
||||
navigate('/worker-main/shifts/', arguments: {'selectedDate': selectedDate});
|
||||
}
|
||||
|
||||
void pushShiftDetails(Shift shift) {
|
||||
pushNamed('/worker-main/shift-details/${shift.id}', arguments: shift);
|
||||
navigate('/worker-main/shift-details/${shift.id}', arguments: shift);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,25 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:design_system/design_system.dart'; // Re-added for UiIcons/Colors as they are used in expanded logic
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:staff_shifts/staff_shifts.dart';
|
||||
import '../blocs/shift_details/shift_details_bloc.dart';
|
||||
import '../blocs/shift_details/shift_details_event.dart';
|
||||
import '../blocs/shift_details/shift_details_state.dart';
|
||||
|
||||
class ShiftDetailsPage extends StatelessWidget {
|
||||
class ShiftDetailsPage extends StatefulWidget {
|
||||
final String shiftId;
|
||||
final Shift? shift;
|
||||
|
||||
const ShiftDetailsPage({super.key, required this.shiftId, this.shift});
|
||||
|
||||
@override
|
||||
State<ShiftDetailsPage> createState() => _ShiftDetailsPageState();
|
||||
}
|
||||
|
||||
class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
||||
bool _actionDialogOpen = false;
|
||||
bool _isApplying = false;
|
||||
|
||||
String _formatTime(String time) {
|
||||
if (time.isEmpty) return '';
|
||||
try {
|
||||
@@ -122,24 +131,37 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ShiftDetailsBloc>(
|
||||
create: (_) =>
|
||||
Modular.get<ShiftDetailsBloc>()..add(LoadShiftDetailsEvent(shiftId)),
|
||||
Modular.get<ShiftDetailsBloc>()
|
||||
..add(
|
||||
LoadShiftDetailsEvent(
|
||||
widget.shiftId,
|
||||
roleId: widget.shift?.roleId,
|
||||
),
|
||||
),
|
||||
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
|
||||
listener: (context, state) {
|
||||
if (state is ShiftActionSuccess || state is ShiftDetailsError) {
|
||||
_closeActionDialog(context);
|
||||
}
|
||||
if (state is ShiftActionSuccess) {
|
||||
_isApplying = false;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: const Color(0xFF10B981),
|
||||
),
|
||||
);
|
||||
Modular.to.pop(true); // Return outcome
|
||||
Modular.to.navigateToShiftsHome(selectedDate: state.shiftDate);
|
||||
} else if (state is ShiftDetailsError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: const Color(0xFFEF4444),
|
||||
),
|
||||
);
|
||||
if (_isApplying || widget.shift == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: const Color(0xFFEF4444),
|
||||
),
|
||||
);
|
||||
}
|
||||
_isApplying = false;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<ShiftDetailsBloc, ShiftDetailsState>(
|
||||
@@ -154,7 +176,7 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
if (state is ShiftDetailsLoaded) {
|
||||
displayShift = state.shift;
|
||||
} else {
|
||||
displayShift = shift;
|
||||
displayShift = widget.shift;
|
||||
}
|
||||
|
||||
if (displayShift == null) {
|
||||
@@ -164,7 +186,8 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
final duration = _calculateDuration(displayShift);
|
||||
final estimatedTotal = (displayShift.hourlyRate) * duration;
|
||||
final estimatedTotal =
|
||||
displayShift.totalValue ?? (displayShift.hourlyRate * duration);
|
||||
final openSlots =
|
||||
(displayShift.requiredSlots ?? 0) -
|
||||
(displayShift.filledSlots ?? 0);
|
||||
@@ -172,8 +195,8 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: displayShift.title,
|
||||
showBackButton: true,
|
||||
centerTitle: false,
|
||||
onLeadingPressed: () => Modular.to.navigateToShiftsHome(),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
@@ -396,14 +419,14 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
displayShift.location.isEmpty
|
||||
? "TBD"
|
||||
: displayShift.locationAddress,
|
||||
style: UiTypography.title1m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
displayShift.location.isEmpty
|
||||
? "TBD"
|
||||
: displayShift.locationAddress,
|
||||
style: UiTypography.title1m.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -438,41 +461,53 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () =>
|
||||
_declineShift(context, displayShift!.id),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFFEF4444),
|
||||
side: const BorderSide(
|
||||
color: Color(0xFFEF4444),
|
||||
if (displayShift!.status != 'confirmed' &&
|
||||
displayShift!.hasApplied != true &&
|
||||
(displayShift!.requiredSlots == null ||
|
||||
displayShift!.filledSlots == null ||
|
||||
displayShift!.filledSlots! <
|
||||
displayShift!.requiredSlots!))
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => _declineShift(
|
||||
context,
|
||||
displayShift!.id,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFFEF4444),
|
||||
side: const BorderSide(
|
||||
color: Color(0xFFEF4444),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
child: const Text("Decline"),
|
||||
),
|
||||
child: const Text("Decline"),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () =>
|
||||
_bookShift(context, displayShift!.id),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF10B981),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _bookShift(
|
||||
context,
|
||||
displayShift!,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(
|
||||
0xFF10B981,
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
child: const Text("Book Shift"),
|
||||
),
|
||||
child: const Text("Book Shift"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 10,
|
||||
),
|
||||
@@ -489,7 +524,10 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _bookShift(BuildContext context, String id) {
|
||||
void _bookShift(
|
||||
BuildContext context,
|
||||
Shift shift,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
@@ -497,15 +535,20 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
content: const Text('Do you want to instantly book this shift?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
BlocProvider.of<ShiftDetailsBloc>(
|
||||
context,
|
||||
).add(BookShiftDetailsEvent(id));
|
||||
Modular.to.pop();
|
||||
_showApplyingDialog(context, shift);
|
||||
BlocProvider.of<ShiftDetailsBloc>(context).add(
|
||||
BookShiftDetailsEvent(
|
||||
shift.id,
|
||||
roleId: shift.roleId,
|
||||
date: DateTime.tryParse(shift.date),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF10B981),
|
||||
@@ -527,12 +570,11 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
BlocProvider.of<ShiftDetailsBloc>(
|
||||
context,
|
||||
).add(DeclineShiftDetailsEvent(id));
|
||||
@@ -546,4 +588,62 @@ class ShiftDetailsPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showApplyingDialog(BuildContext context, Shift shift) {
|
||||
if (_actionDialogOpen) return;
|
||||
_actionDialogOpen = true;
|
||||
_isApplying = true;
|
||||
showDialog(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Applying'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 36,
|
||||
width: 36,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
shift.title,
|
||||
style: UiTypography.body2b.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${_formatDate(shift.date)} • ${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}',
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (shift.clientName.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
shift.clientName,
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
).then((_) {
|
||||
_actionDialogOpen = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _closeActionDialog(BuildContext context) {
|
||||
if (!_actionDialogOpen) return;
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
_actionDialogOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import '../styles/shifts_styles.dart';
|
||||
|
||||
class ShiftsPage extends StatefulWidget {
|
||||
final String? initialTab;
|
||||
const ShiftsPage({super.key, this.initialTab});
|
||||
final DateTime? selectedDate;
|
||||
const ShiftsPage({super.key, this.initialTab, this.selectedDate});
|
||||
|
||||
@override
|
||||
State<ShiftsPage> createState() => _ShiftsPageState();
|
||||
@@ -19,13 +20,18 @@ class ShiftsPage extends StatefulWidget {
|
||||
|
||||
class _ShiftsPageState extends State<ShiftsPage> {
|
||||
late String _activeTab;
|
||||
DateTime? _selectedDate;
|
||||
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_activeTab = widget.initialTab ?? 'myshifts';
|
||||
_selectedDate = widget.selectedDate;
|
||||
_bloc.add(LoadShiftsEvent());
|
||||
if (_activeTab == 'history') {
|
||||
_bloc.add(LoadHistoryShiftsEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -36,6 +42,11 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
_activeTab = widget.initialTab!;
|
||||
});
|
||||
}
|
||||
if (widget.selectedDate != null && widget.selectedDate != _selectedDate) {
|
||||
setState(() {
|
||||
_selectedDate = widget.selectedDate;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -59,6 +70,12 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
final List<Shift> historyShifts = (state is ShiftsLoaded)
|
||||
? state.historyShifts
|
||||
: [];
|
||||
final bool historyLoading = (state is ShiftsLoaded)
|
||||
? state.historyLoading
|
||||
: false;
|
||||
final bool historyLoaded = (state is ShiftsLoaded)
|
||||
? state.historyLoaded
|
||||
: false;
|
||||
|
||||
// Note: "filteredJobs" logic moved to FindShiftsTab
|
||||
// Note: Calendar logic moved to MyShiftsTab
|
||||
@@ -103,7 +120,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
"find",
|
||||
"Find Shifts",
|
||||
UiIcons.search,
|
||||
availableJobs.length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs.
|
||||
availableJobs
|
||||
.length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs.
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildTab(
|
||||
@@ -111,6 +129,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
"History",
|
||||
UiIcons.clock,
|
||||
historyShifts.length,
|
||||
showCount: historyLoaded,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -124,11 +143,12 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _buildTabContent(
|
||||
myShifts,
|
||||
pendingAssignments,
|
||||
cancelledShifts,
|
||||
availableJobs,
|
||||
historyShifts,
|
||||
),
|
||||
pendingAssignments,
|
||||
cancelledShifts,
|
||||
availableJobs,
|
||||
historyShifts,
|
||||
historyLoading,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -144,6 +164,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
List<Shift> cancelledShifts,
|
||||
List<Shift> availableJobs,
|
||||
List<Shift> historyShifts,
|
||||
bool historyLoading,
|
||||
) {
|
||||
switch (_activeTab) {
|
||||
case 'myshifts':
|
||||
@@ -151,25 +172,36 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
myShifts: myShifts,
|
||||
pendingAssignments: pendingAssignments,
|
||||
cancelledShifts: cancelledShifts,
|
||||
initialDate: _selectedDate,
|
||||
);
|
||||
case 'find':
|
||||
return FindShiftsTab(
|
||||
availableJobs: availableJobs,
|
||||
);
|
||||
return FindShiftsTab(availableJobs: availableJobs);
|
||||
case 'history':
|
||||
return HistoryShiftsTab(
|
||||
historyShifts: historyShifts,
|
||||
);
|
||||
if (historyLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return HistoryShiftsTab(historyShifts: historyShifts);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTab(String id, String label, IconData icon, int count) {
|
||||
Widget _buildTab(
|
||||
String id,
|
||||
String label,
|
||||
IconData icon,
|
||||
int count, {
|
||||
bool showCount = true,
|
||||
}) {
|
||||
final isActive = _activeTab == id;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _activeTab = id),
|
||||
onTap: () {
|
||||
setState(() => _activeTab = id);
|
||||
if (id == 'history') {
|
||||
_bloc.add(LoadHistoryShiftsEvent());
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
@@ -199,27 +231,32 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
constraints: const BoxConstraints(minWidth: 18),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive
|
||||
? AppColors.krowBlue.withAlpha((0.1 * 255).round())
|
||||
: Colors.white.withAlpha((0.2 * 255).round()),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"$count",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isActive ? AppColors.krowBlue : Colors.white,
|
||||
if (showCount) ...[
|
||||
const SizedBox(width: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 18),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive
|
||||
? AppColors.krowBlue.withAlpha((0.1 * 255).round())
|
||||
: Colors.white.withAlpha((0.2 * 255).round()),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"$count",
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isActive ? AppColors.krowBlue : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -175,7 +175,7 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -40,7 +40,7 @@ class HistoryShiftsTab extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user