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:
Achintha Isuru
2026-02-01 22:18:43 -05:00
committed by GitHub
109 changed files with 25853 additions and 24849 deletions

View 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

View File

@@ -5,42 +5,6 @@
"storage_bucket": "krow-workforce-dev.firebasestorage.app" "storage_bucket": "krow-workforce-dev.firebasestorage.app"
}, },
"client": [ "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": { "client_info": {
"mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db", "mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db",
@@ -67,10 +31,10 @@
"client_type": 3 "client_type": 3
}, },
{ {
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", "client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
"client_type": 2, "client_type": 2,
"ios_info": { "ios_info": {
"bundle_id": "com.krow.app.staff.dev" "bundle_id": "com.krowwithus.staff"
} }
} }
] ]
@@ -103,10 +67,10 @@
"client_type": 3 "client_type": 3
}, },
{ {
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", "client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
"client_type": 2, "client_type": 2,
"ios_info": { "ios_info": {
"bundle_id": "com.krow.app.staff.dev" "bundle_id": "com.krowwithus.staff"
} }
} }
] ]
@@ -139,10 +103,10 @@
"client_type": 3 "client_type": 3
}, },
{ {
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", "client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
"client_type": 2, "client_type": 2,
"ios_info": { "ios_info": {
"bundle_id": "com.krow.app.staff.dev" "bundle_id": "com.krowwithus.staff"
} }
} }
] ]
@@ -151,12 +115,20 @@
}, },
{ {
"client_info": { "client_info": {
"mobilesdk_app_id": "1:933560802882:android:d26bde4ee337b0b17757db", "mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db",
"android_client_info": { "android_client_info": {
"package_name": "com.krowwithus.krow_workforce.dev" "package_name": "com.krowwithus.staff"
} }
}, },
"oauth_client": [ "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_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com",
"client_type": 3 "client_type": 3
@@ -175,10 +147,10 @@
"client_type": 3 "client_type": 3
}, },
{ {
"client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", "client_id": "933560802882-29olj9ku64jbe9h7flinha6hbi8qrluh.apps.googleusercontent.com",
"client_type": 2, "client_type": 2,
"ios_info": { "ios_info": {
"bundle_id": "com.krow.app.staff.dev" "bundle_id": "com.krowwithus.staff"
} }
} }
] ]

View File

@@ -14,6 +14,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -42,6 +43,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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 */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
221E00B70DE845BE3D50D0A0 /* GoogleService-Info.plist */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -216,6 +219,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
E8C1A28BFABAEE32FB779C9A /* GoogleService-Info.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

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

View 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',
);
}

View File

@@ -12,10 +12,15 @@ import 'package:client_hubs/client_hubs.dart' as client_hubs;
import 'package:client_create_order/client_create_order.dart' import 'package:client_create_order/client_create_order.dart'
as client_create_order; as client_create_order;
import 'package:firebase_core/firebase_core.dart'; 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 { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); await Firebase.initializeApp(
options: kIsWeb ? DefaultFirebaseOptions.currentPlatform : null,
);
runApp(ModularApp(module: AppModule(), child: const AppWidget())); runApp(ModularApp(module: AppModule(), child: const AppWidget()));
} }
@@ -54,34 +59,38 @@ class AppWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<core_localization.LocaleBloc>( return WebMobileFrame(
create: (BuildContext context) => appName: 'KROW Client\nApplication',
Modular.get<core_localization.LocaleBloc>(), logo: Image.asset('assets/logo.png'),
child: child: BlocProvider<core_localization.LocaleBloc>(
BlocBuilder< create: (BuildContext context) =>
core_localization.LocaleBloc, Modular.get<core_localization.LocaleBloc>(),
core_localization.LocaleState child:
>( BlocBuilder<
builder: core_localization.LocaleBloc,
(BuildContext context, core_localization.LocaleState state) { core_localization.LocaleState
return core_localization.TranslationProvider( >(
child: MaterialApp.router( builder:
debugShowCheckedModeBanner: false, (BuildContext context, core_localization.LocaleState state) {
title: "Krow Client", return core_localization.TranslationProvider(
theme: UiTheme.light, child: MaterialApp.router(
routerConfig: Modular.routerConfig, debugShowCheckedModeBanner: false,
locale: state.locale, title: "Krow Client",
supportedLocales: state.supportedLocales, theme: UiTheme.light,
localizationsDelegates: routerConfig: Modular.routerConfig,
const <LocalizationsDelegate<dynamic>>[ locale: state.locale,
GlobalMaterialLocalizations.delegate, supportedLocales: state.supportedLocales,
GlobalWidgetsLocalizations.delegate, localizationsDelegates:
GlobalCupertinoLocalizations.delegate, const <LocalizationsDelegate<dynamic>>[
], GlobalMaterialLocalizations.delegate,
), GlobalWidgetsLocalizations.delegate,
); GlobalCupertinoLocalizations.delegate,
}, ],
), ),
);
},
),
),
); );
} }
} }

View File

@@ -32,6 +32,8 @@ dependencies:
path: ../../packages/features/client/hubs path: ../../packages/features/client/hubs
client_create_order: client_create_order:
path: ../../packages/features/client/create_order path: ../../packages/features/client/create_order
krow_core:
path: ../../packages/core
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
flutter_modular: ^6.3.2 flutter_modular: ^6.3.2

View File

@@ -33,6 +33,29 @@
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body> <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> <script src="flutter_bootstrap.js" async></script>
</body> </body>
</html> </html>

View File

@@ -5,42 +5,6 @@
"storage_bucket": "krow-workforce-dev.firebasestorage.app" "storage_bucket": "krow-workforce-dev.firebasestorage.app"
}, },
"client": [ "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": { "client_info": {
"mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db", "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": { "client_info": {
"mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db", "mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db",

View File

@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 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 */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 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>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -94,6 +96,7 @@
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
9F0B07DEC91B141354438F79 /* GoogleService-Info.plist */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -216,6 +219,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
1E967D034ADA3A16EF82CB3E /* GoogleService-Info.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

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

View 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',
);
}

View File

@@ -5,13 +5,17 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krowwithus_staff/firebase_options.dart';
import 'package:staff_authentication/staff_authentication.dart' import 'package:staff_authentication/staff_authentication.dart'
as staff_authentication; as staff_authentication;
import 'package:staff_main/staff_main.dart' as staff_main; import 'package:staff_main/staff_main.dart' as staff_main;
import 'package:krow_core/core.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(ModularApp(module: AppModule(), child: const AppWidget())); runApp(ModularApp(module: AppModule(), child: const AppWidget()));
} }
@@ -34,33 +38,37 @@ class AppWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<core_localization.LocaleBloc>( return WebMobileFrame(
create: (BuildContext context) => appName: 'KROW Staff\nApplication',
Modular.get<core_localization.LocaleBloc>(), logo: Image.asset('assets/logo.png'),
child: child: BlocProvider<core_localization.LocaleBloc>(
BlocBuilder< create: (BuildContext context) =>
core_localization.LocaleBloc, Modular.get<core_localization.LocaleBloc>(),
core_localization.LocaleState child:
>( BlocBuilder<
builder: core_localization.LocaleBloc,
(BuildContext context, core_localization.LocaleState state) { core_localization.LocaleState
return core_localization.TranslationProvider( >(
child: MaterialApp.router( builder:
title: "KROW Staff", (BuildContext context, core_localization.LocaleState state) {
theme: UiTheme.light, return core_localization.TranslationProvider(
routerConfig: Modular.routerConfig, child: MaterialApp.router(
locale: state.locale, title: "KROW Staff",
supportedLocales: state.supportedLocales, theme: UiTheme.light,
localizationsDelegates: routerConfig: Modular.routerConfig,
const <LocalizationsDelegate<dynamic>>[ locale: state.locale,
GlobalMaterialLocalizations.delegate, supportedLocales: state.supportedLocales,
GlobalWidgetsLocalizations.delegate, localizationsDelegates:
GlobalCupertinoLocalizations.delegate, const <LocalizationsDelegate<dynamic>>[
], GlobalMaterialLocalizations.delegate,
), GlobalWidgetsLocalizations.delegate,
); GlobalCupertinoLocalizations.delegate,
}, ],
), ),
);
},
),
),
); );
} }
} }

View File

@@ -1,21 +1,143 @@
PODS: 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) - 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): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
DEPENDENCIES: 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`) - 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`) - 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: 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: FlutterMacOS:
:path: Flutter/ephemeral :path: Flutter/ephemeral
geolocator_apple:
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
shared_preferences_foundation: shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
SPEC CHECKSUMS: 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 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: 02d6e866e90bc236f48a703a041dfe43e6221a29
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009

View File

@@ -11,10 +11,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.8 # Architecture Packages
flutter_modular: ^6.3.0
# Architecture Packages
design_system: design_system:
path: ../../packages/design_system path: ../../packages/design_system
core_localization: core_localization:
@@ -27,6 +24,14 @@ dependencies:
path: ../../packages/features/staff/availability path: ../../packages/features/staff/availability
staff_clock_in: staff_clock_in:
path: ../../packages/features/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: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -34,5 +34,28 @@
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <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> </body>
</html> </html>

View File

@@ -3,3 +3,4 @@ library core;
export 'src/domain/arguments/usecase_argument.dart'; export 'src/domain/arguments/usecase_argument.dart';
export 'src/domain/usecases/usecase.dart'; export 'src/domain/usecases/usecase.dart';
export 'src/utils/date_time_utils.dart'; export 'src/utils/date_time_utils.dart';
export 'src/presentation/widgets/web_mobile_frame.dart';

View File

@@ -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,
),
],
),
),
),
),
],
),
),
);
}
}

View File

@@ -11,3 +11,5 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
design_system:
path: ../design_system

View File

@@ -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;
}

View File

@@ -1,16 +1,16 @@
# Basic Usage # Basic Usage
```dart ```dart
ExampleConnector.instance.createRecentPayment(createRecentPaymentVariables).execute();
ExampleConnector.instance.updateRecentPayment(updateRecentPaymentVariables).execute();
ExampleConnector.instance.deleteRecentPayment(deleteRecentPaymentVariables).execute();
ExampleConnector.instance.CreateStaff(createStaffVariables).execute(); ExampleConnector.instance.CreateStaff(createStaffVariables).execute();
ExampleConnector.instance.UpdateStaff(updateStaffVariables).execute(); ExampleConnector.instance.UpdateStaff(updateStaffVariables).execute();
ExampleConnector.instance.DeleteStaff(deleteStaffVariables).execute(); ExampleConnector.instance.DeleteStaff(deleteStaffVariables).execute();
ExampleConnector.instance.getStaffDocumentByKey(getStaffDocumentByKeyVariables).execute(); ExampleConnector.instance.listStaffAvailabilities(listStaffAvailabilitiesVariables).execute();
ExampleConnector.instance.listStaffDocumentsByStaffId(listStaffDocumentsByStaffIdVariables).execute(); ExampleConnector.instance.listStaffAvailabilitiesByStaffId(listStaffAvailabilitiesByStaffIdVariables).execute();
ExampleConnector.instance.listStaffDocumentsByDocumentType(listStaffDocumentsByDocumentTypeVariables).execute(); ExampleConnector.instance.getStaffAvailabilityByKey(getStaffAvailabilityByKeyVariables).execute();
ExampleConnector.instance.listStaffDocumentsByStatus(listStaffDocumentsByStatusVariables).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: This is an example of a mutation with an optional field:
```dart ```dart
await ExampleConnector.instance.updateAttireOption({ ... }) await ExampleConnector.instance.searchInvoiceTemplatesByOwnerAndName({ ... })
.itemId(...) .offset(...)
.execute(); .execute();
``` ```

View File

@@ -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,
});
}

View File

@@ -4,6 +4,8 @@ class GetApplicationsByStaffIdVariablesBuilder {
String staffId; String staffId;
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson); Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
Optional<int> _limit = 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) { final FirebaseDataConnect _dataConnect; GetApplicationsByStaffIdVariablesBuilder offset(int? t) {
_offset.value = t; _offset.value = t;
@@ -13,6 +15,14 @@ class GetApplicationsByStaffIdVariablesBuilder {
_limit.value = t; _limit.value = t;
return this; 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,}); GetApplicationsByStaffIdVariablesBuilder(this._dataConnect, {required this.staffId,});
Deserializer<GetApplicationsByStaffIdData> dataDeserializer = (dynamic json) => GetApplicationsByStaffIdData.fromJson(jsonDecode(json)); Deserializer<GetApplicationsByStaffIdData> dataDeserializer = (dynamic json) => GetApplicationsByStaffIdData.fromJson(jsonDecode(json));
@@ -22,7 +32,7 @@ class GetApplicationsByStaffIdVariablesBuilder {
} }
QueryRef<GetApplicationsByStaffIdData, GetApplicationsByStaffIdVariables> ref() { 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); return _dataConnect.query("getApplicationsByStaffId", dataDeserializer, varsSerializer, vars);
} }
} }
@@ -132,6 +142,10 @@ class GetApplicationsByStaffIdApplicationsShift {
final Timestamp? endTime; final Timestamp? endTime;
final String? location; final String? location;
final EnumValue<ShiftStatus>? status; final EnumValue<ShiftStatus>? status;
final int? durationDays;
final String? description;
final double? latitude;
final double? longitude;
final GetApplicationsByStaffIdApplicationsShiftOrder order; final GetApplicationsByStaffIdApplicationsShiftOrder order;
GetApplicationsByStaffIdApplicationsShift.fromJson(dynamic json): GetApplicationsByStaffIdApplicationsShift.fromJson(dynamic json):
@@ -142,6 +156,10 @@ class GetApplicationsByStaffIdApplicationsShift {
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']), endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
location = json['location'] == null ? null : nativeFromJson<String>(json['location']), location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']), 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']); order = GetApplicationsByStaffIdApplicationsShiftOrder.fromJson(json['order']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -160,11 +178,15 @@ class GetApplicationsByStaffIdApplicationsShift {
endTime == otherTyped.endTime && endTime == otherTyped.endTime &&
location == otherTyped.location && location == otherTyped.location &&
status == otherTyped.status && status == otherTyped.status &&
durationDays == otherTyped.durationDays &&
description == otherTyped.description &&
latitude == otherTyped.latitude &&
longitude == otherTyped.longitude &&
order == otherTyped.order; order == otherTyped.order;
} }
@override @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() { Map<String, dynamic> toJson() {
@@ -188,6 +210,18 @@ class GetApplicationsByStaffIdApplicationsShift {
shiftStatusSerializer(status!) 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(); json['order'] = order.toJson();
return json; return json;
} }
@@ -200,6 +234,10 @@ class GetApplicationsByStaffIdApplicationsShift {
this.endTime, this.endTime,
this.location, this.location,
this.status, this.status,
this.durationDays,
this.description,
this.latitude,
this.longitude,
required this.order, required this.order,
}); });
} }
@@ -314,12 +352,14 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
final String businessName; final String businessName;
final String? email; final String? email;
final String? contactName; final String? contactName;
final String? companyLogoUrl;
GetApplicationsByStaffIdApplicationsShiftOrderBusiness.fromJson(dynamic json): GetApplicationsByStaffIdApplicationsShiftOrderBusiness.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']), id = nativeFromJson<String>(json['id']),
businessName = nativeFromJson<String>(json['businessName']), businessName = nativeFromJson<String>(json['businessName']),
email = json['email'] == null ? null : nativeFromJson<String>(json['email']), 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
if(identical(this, other)) { if(identical(this, other)) {
@@ -333,11 +373,12 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
return id == otherTyped.id && return id == otherTyped.id &&
businessName == otherTyped.businessName && businessName == otherTyped.businessName &&
email == otherTyped.email && email == otherTyped.email &&
contactName == otherTyped.contactName; contactName == otherTyped.contactName &&
companyLogoUrl == otherTyped.companyLogoUrl;
} }
@override @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() { Map<String, dynamic> toJson() {
@@ -350,6 +391,9 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
if (contactName != null) { if (contactName != null) {
json['contactName'] = nativeToJson<String?>(contactName); json['contactName'] = nativeToJson<String?>(contactName);
} }
if (companyLogoUrl != null) {
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
}
return json; return json;
} }
@@ -358,6 +402,7 @@ class GetApplicationsByStaffIdApplicationsShiftOrderBusiness {
required this.businessName, required this.businessName,
this.email, this.email,
this.contactName, this.contactName,
this.companyLogoUrl,
}); });
} }
@@ -569,6 +614,8 @@ class GetApplicationsByStaffIdVariables {
final String staffId; final String staffId;
late final Optional<int>offset; late final Optional<int>offset;
late final Optional<int>limit; 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.') @Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
GetApplicationsByStaffIdVariables.fromJson(Map<String, dynamic> json): GetApplicationsByStaffIdVariables.fromJson(Map<String, dynamic> json):
@@ -583,6 +630,14 @@ class GetApplicationsByStaffIdVariables {
limit = Optional.optional(nativeFromJson, nativeToJson); limit = Optional.optional(nativeFromJson, nativeToJson);
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']); 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -596,11 +651,13 @@ class GetApplicationsByStaffIdVariables {
final GetApplicationsByStaffIdVariables otherTyped = other as GetApplicationsByStaffIdVariables; final GetApplicationsByStaffIdVariables otherTyped = other as GetApplicationsByStaffIdVariables;
return staffId == otherTyped.staffId && return staffId == otherTyped.staffId &&
offset == otherTyped.offset && offset == otherTyped.offset &&
limit == otherTyped.limit; limit == otherTyped.limit &&
dayStart == otherTyped.dayStart &&
dayEnd == otherTyped.dayEnd;
} }
@override @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() { Map<String, dynamic> toJson() {
@@ -612,6 +669,12 @@ class GetApplicationsByStaffIdVariables {
if(limit.state == OptionalState.set) { if(limit.state == OptionalState.set) {
json['limit'] = limit.toJson(); json['limit'] = limit.toJson();
} }
if(dayStart.state == OptionalState.set) {
json['dayStart'] = dayStart.toJson();
}
if(dayEnd.state == OptionalState.set) {
json['dayEnd'] = dayEnd.toJson();
}
return json; return json;
} }
@@ -619,6 +682,8 @@ class GetApplicationsByStaffIdVariables {
required this.staffId, required this.staffId,
required this.offset, required this.offset,
required this.limit, required this.limit,
required this.dayStart,
required this.dayEnd,
}); });
} }

View File

@@ -253,13 +253,15 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
final String? notes; final String? notes;
final GetShiftRoleByIdShiftRoleShiftOrderBusiness business; final GetShiftRoleByIdShiftRoleShiftOrderBusiness business;
final GetShiftRoleByIdShiftRoleShiftOrderVendor? vendor; final GetShiftRoleByIdShiftRoleShiftOrderVendor? vendor;
final GetShiftRoleByIdShiftRoleShiftOrderTeamHub teamHub;
GetShiftRoleByIdShiftRoleShiftOrder.fromJson(dynamic json): GetShiftRoleByIdShiftRoleShiftOrder.fromJson(dynamic json):
recurringDays = json['recurringDays'] == null ? null : AnyValue.fromJson(json['recurringDays']), recurringDays = json['recurringDays'] == null ? null : AnyValue.fromJson(json['recurringDays']),
permanentDays = json['permanentDays'] == null ? null : AnyValue.fromJson(json['permanentDays']), permanentDays = json['permanentDays'] == null ? null : AnyValue.fromJson(json['permanentDays']),
notes = json['notes'] == null ? null : nativeFromJson<String>(json['notes']), notes = json['notes'] == null ? null : nativeFromJson<String>(json['notes']),
business = GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(json['business']), 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
if(identical(this, other)) { if(identical(this, other)) {
@@ -274,11 +276,12 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
permanentDays == otherTyped.permanentDays && permanentDays == otherTyped.permanentDays &&
notes == otherTyped.notes && notes == otherTyped.notes &&
business == otherTyped.business && business == otherTyped.business &&
vendor == otherTyped.vendor; vendor == otherTyped.vendor &&
teamHub == otherTyped.teamHub;
} }
@override @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() { Map<String, dynamic> toJson() {
@@ -296,6 +299,7 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
if (vendor != null) { if (vendor != null) {
json['vendor'] = vendor!.toJson(); json['vendor'] = vendor!.toJson();
} }
json['teamHub'] = teamHub.toJson();
return json; return json;
} }
@@ -305,6 +309,7 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
this.notes, this.notes,
required this.business, required this.business,
this.vendor, this.vendor,
required this.teamHub,
}); });
} }
@@ -312,10 +317,12 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
class GetShiftRoleByIdShiftRoleShiftOrderBusiness { class GetShiftRoleByIdShiftRoleShiftOrderBusiness {
final String id; final String id;
final String businessName; final String businessName;
final String? companyLogoUrl;
GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(dynamic json): GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']), 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
if(identical(this, other)) { if(identical(this, other)) {
@@ -327,23 +334,28 @@ class GetShiftRoleByIdShiftRoleShiftOrderBusiness {
final GetShiftRoleByIdShiftRoleShiftOrderBusiness otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderBusiness; final GetShiftRoleByIdShiftRoleShiftOrderBusiness otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderBusiness;
return id == otherTyped.id && return id == otherTyped.id &&
businessName == otherTyped.businessName; businessName == otherTyped.businessName &&
companyLogoUrl == otherTyped.companyLogoUrl;
} }
@override @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> toJson() {
Map<String, dynamic> json = {}; Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id); json['id'] = nativeToJson<String>(id);
json['businessName'] = nativeToJson<String>(businessName); json['businessName'] = nativeToJson<String>(businessName);
if (companyLogoUrl != null) {
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
}
return json; return json;
} }
GetShiftRoleByIdShiftRoleShiftOrderBusiness({ GetShiftRoleByIdShiftRoleShiftOrderBusiness({
required this.id, required this.id,
required this.businessName, 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 @immutable
class GetShiftRoleByIdData { class GetShiftRoleByIdData {
final GetShiftRoleByIdShiftRole? shiftRole; final GetShiftRoleByIdShiftRole? shiftRole;

View File

@@ -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,
});
}

View File

@@ -204,6 +204,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
final String? locationAddress; final String? locationAddress;
final String? description; final String? description;
final String orderId; final String orderId;
final EnumValue<ShiftStatus>? status;
final int? durationDays;
final ListShiftRolesByVendorIdShiftRolesShiftOrder order; final ListShiftRolesByVendorIdShiftRolesShiftOrder order;
ListShiftRolesByVendorIdShiftRolesShift.fromJson(dynamic json): ListShiftRolesByVendorIdShiftRolesShift.fromJson(dynamic json):
@@ -214,6 +216,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
locationAddress = json['locationAddress'] == null ? null : nativeFromJson<String>(json['locationAddress']), locationAddress = json['locationAddress'] == null ? null : nativeFromJson<String>(json['locationAddress']),
description = json['description'] == null ? null : nativeFromJson<String>(json['description']), description = json['description'] == null ? null : nativeFromJson<String>(json['description']),
orderId = nativeFromJson<String>(json['orderId']), 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']); order = ListShiftRolesByVendorIdShiftRolesShiftOrder.fromJson(json['order']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -232,11 +236,13 @@ class ListShiftRolesByVendorIdShiftRolesShift {
locationAddress == otherTyped.locationAddress && locationAddress == otherTyped.locationAddress &&
description == otherTyped.description && description == otherTyped.description &&
orderId == otherTyped.orderId && orderId == otherTyped.orderId &&
status == otherTyped.status &&
durationDays == otherTyped.durationDays &&
order == otherTyped.order; order == otherTyped.order;
} }
@override @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() { Map<String, dynamic> toJson() {
@@ -256,6 +262,14 @@ class ListShiftRolesByVendorIdShiftRolesShift {
json['description'] = nativeToJson<String?>(description); json['description'] = nativeToJson<String?>(description);
} }
json['orderId'] = nativeToJson<String>(orderId); json['orderId'] = nativeToJson<String>(orderId);
if (status != null) {
json['status'] =
shiftStatusSerializer(status!)
;
}
if (durationDays != null) {
json['durationDays'] = nativeToJson<int?>(durationDays);
}
json['order'] = order.toJson(); json['order'] = order.toJson();
return json; return json;
} }
@@ -268,6 +282,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
this.locationAddress, this.locationAddress,
this.description, this.description,
required this.orderId, required this.orderId,
this.status,
this.durationDays,
required this.order, required this.order,
}); });
} }

View File

@@ -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,
});
}

View File

@@ -3,10 +3,12 @@ import 'package:krow_domain/krow_domain.dart' as domain;
class StaffSession { class StaffSession {
final domain.User user; final domain.User user;
final domain.Staff? staff; final domain.Staff? staff;
final String? ownerId;
const StaffSession({ const StaffSession({
required this.user, required this.user,
this.staff, this.staff,
this.ownerId,
}); });
} }

View File

@@ -63,6 +63,9 @@ class UiIcons {
/// Checkmark icon /// Checkmark icon
static const IconData check = _IconLib.check; static const IconData check = _IconLib.check;
/// Checkmark circle icon
static const IconData checkCircle = _IconLib.checkCircle;
/// X/Cancel icon /// X/Cancel icon
static const IconData close = _IconLib.x; static const IconData close = _IconLib.x;

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'ui_colors.dart'; import 'ui_colors.dart';
import 'ui_typography.dart';
import 'ui_constants.dart'; import 'ui_constants.dart';
import 'ui_typography.dart';
/// The main entry point for the Staff Design System theme. /// The main entry point for the Staff Design System theme.
/// Assembles colors, typography, and constants into a comprehensive Material 3 theme. /// Assembles colors, typography, and constants into a comprehensive Material 3 theme.

View File

@@ -50,7 +50,7 @@ class UiButton extends StatelessWidget {
this.trailingIcon, this.trailingIcon,
this.style, this.style,
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.medium, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
}) : assert( }) : assert(
text != null || child != null, text != null || child != null,
@@ -67,7 +67,7 @@ class UiButton extends StatelessWidget {
this.trailingIcon, this.trailingIcon,
this.style, this.style,
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.medium, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
}) : buttonBuilder = _elevatedButtonBuilder, }) : buttonBuilder = _elevatedButtonBuilder,
assert( assert(
@@ -85,7 +85,7 @@ class UiButton extends StatelessWidget {
this.trailingIcon, this.trailingIcon,
this.style, this.style,
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.medium, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
}) : buttonBuilder = _outlinedButtonBuilder, }) : buttonBuilder = _outlinedButtonBuilder,
assert( assert(
@@ -103,7 +103,7 @@ class UiButton extends StatelessWidget {
this.trailingIcon, this.trailingIcon,
this.style, this.style,
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.medium, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
}) : buttonBuilder = _textButtonBuilder, }) : buttonBuilder = _textButtonBuilder,
assert( assert(
@@ -121,7 +121,7 @@ class UiButton extends StatelessWidget {
this.trailingIcon, this.trailingIcon,
this.style, this.style,
this.iconSize = 20, this.iconSize = 20,
this.size = UiButtonSize.medium, this.size = UiButtonSize.large,
this.fullWidth = false, this.fullWidth = false,
}) : buttonBuilder = _textButtonBuilder, }) : buttonBuilder = _textButtonBuilder,
assert( assert(
@@ -132,10 +132,14 @@ class UiButton extends StatelessWidget {
@override @override
/// Builds the button UI. /// Builds the button UI.
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ButtonStyle? mergedStyle = style != null
? _getSizeStyle().merge(style)
: _getSizeStyle();
final Widget button = buttonBuilder( final Widget button = buttonBuilder(
context, context,
onPressed, onPressed,
style, mergedStyle,
_buildButtonContent(), _buildButtonContent(),
); );
@@ -146,6 +150,65 @@ class UiButton extends StatelessWidget {
return button; 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. /// Builds the button content with optional leading and trailing icons.
Widget _buildButtonContent() { Widget _buildButtonContent() {
if (child != null) { if (child != null) {
@@ -229,6 +292,9 @@ class UiButton extends StatelessWidget {
/// Defines the size of a [UiButton]. /// Defines the size of a [UiButton].
enum UiButtonSize { enum UiButtonSize {
/// Extra small button (very compact)
extraSmall,
/// Small button (compact) /// Small button (compact)
small, small,

View File

@@ -87,6 +87,11 @@ export 'src/adapters/clock_in/clock_in_adapter.dart';
export 'src/entities/availability/availability_slot.dart'; export 'src/entities/availability/availability_slot.dart';
export 'src/entities/availability/day_availability.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 // Adapters
export 'src/adapters/profile/emergency_contact_adapter.dart'; export 'src/adapters/profile/emergency_contact_adapter.dart';
export 'src/adapters/profile/experience_adapter.dart'; export 'src/adapters/profile/experience_adapter.dart';

View File

@@ -1,10 +1,59 @@
import 'package:intl/intl.dart';
import '../../entities/shifts/shift.dart'; import '../../entities/shifts/shift.dart';
/// Adapter for Shift related data. /// Adapter for Shift related data.
class ShiftAdapter { class ShiftAdapter {
/// Maps application data to a Shift entity.
// 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, /// This method handles the common mapping logic used across different
// we might put the logic in Repo or make this accept dynamic/Map if strictly required. /// repositories when converting application data from Data Connect to
// For now, placeholders or simple status helpers. /// 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,
);
}
} }

View File

@@ -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,
];
}

View File

@@ -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,
];
}

View File

@@ -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];
}

View File

@@ -26,6 +26,9 @@ class Shift extends Equatable {
final int? durationDays; // For multi-day shifts final int? durationDays; // For multi-day shifts
final int? requiredSlots; final int? requiredSlots;
final int? filledSlots; final int? filledSlots;
final String? roleId;
final bool? hasApplied;
final double? totalValue;
const Shift({ const Shift({
required this.id, required this.id,
@@ -53,6 +56,9 @@ class Shift extends Equatable {
this.durationDays, this.durationDays,
this.requiredSlots, this.requiredSlots,
this.filledSlots, this.filledSlots,
this.roleId,
this.hasApplied,
this.totalValue,
}); });
@override @override
@@ -82,6 +88,9 @@ class Shift extends Equatable {
durationDays, durationDays,
requiredSlots, requiredSlots,
filledSlots, filledSlots,
roleId,
hasApplied,
totalValue,
]; ];
} }

View File

@@ -200,6 +200,8 @@ class _BillingViewState extends State<BillingView> {
const SpendingBreakdownCard(), const SpendingBreakdownCard(),
if (state.invoiceHistory.isEmpty) _buildEmptyState(context) if (state.invoiceHistory.isEmpty) _buildEmptyState(context)
else InvoiceHistorySection(invoices: state.invoiceHistory), else InvoiceHistorySection(invoices: state.invoiceHistory),
const SizedBox(height: UiConstants.space32),
], ],
), ),
); );

View File

@@ -1,7 +1,7 @@
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; 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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/coverage_repository.dart'; import '../../domain/repositories/coverage_repository.dart';
import '../../domain/ui_entities/coverage_entities.dart';
/// Implementation of [CoverageRepository] in the Data layer. /// Implementation of [CoverageRepository] in the Data layer.
/// ///
@@ -25,18 +25,14 @@ class CoverageRepositoryImpl implements CoverageRepository {
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async { Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
final String? businessId = final String? businessId =
dc.ClientSessionStore.instance.session?.business?.id; dc.ClientSessionStore.instance.session?.business?.id;
print('Coverage: now=${DateTime.now().toIso8601String()}');
if (businessId == null || businessId.isEmpty) { if (businessId == null || businessId.isEmpty) {
print('Coverage: missing businessId for date=${date.toIso8601String()}');
return <CoverageShift>[]; return <CoverageShift>[];
} }
final DateTime start = DateTime(date.year, date.month, date.day); final DateTime start = DateTime(date.year, date.month, date.day);
final DateTime end = final DateTime end =
DateTime(date.year, date.month, date.day, 23, 59, 59, 999); 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< final fdc.QueryResult<
dc.ListShiftRolesByBusinessAndDateRangeData, dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult = dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
@@ -58,9 +54,6 @@ class CoverageRepositoryImpl implements CoverageRepository {
dayEnd: _toTimestamp(end), dayEnd: _toTimestamp(end),
) )
.execute(); .execute();
print(
'Coverage: ${date.toIso8601String()} staffsApplications=${applicationsResult.data.applications.length}',
);
return _mapCoverageShifts( return _mapCoverageShifts(
shiftRolesResult.data.shiftRoles, shiftRolesResult.data.shiftRoles,
@@ -84,11 +77,16 @@ class CoverageRepositoryImpl implements CoverageRepository {
final List<CoverageWorker> allWorkers = final List<CoverageWorker> allWorkers =
shifts.expand((CoverageShift shift) => shift.workers).toList(); shifts.expand((CoverageShift shift) => shift.workers).toList();
final int totalConfirmed = allWorkers.length; final int totalConfirmed = allWorkers.length;
final int checkedIn = final int checkedIn = allWorkers
allWorkers.where((CoverageWorker w) => w.isCheckedIn).length; .where((CoverageWorker w) => w.status == CoverageWorkerStatus.checkedIn)
final int enRoute = .length;
allWorkers.where((CoverageWorker w) => w.isEnRoute).length; final int enRoute = allWorkers
final int late = allWorkers.where((CoverageWorker w) => w.isLate).length; .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( return CoverageStats(
totalNeeded: totalNeeded, totalNeeded: totalNeeded,
@@ -172,24 +170,32 @@ class CoverageRepositoryImpl implements CoverageRepository {
.toList(); .toList();
} }
String _mapWorkerStatus( CoverageWorkerStatus _mapWorkerStatus(
dc.EnumValue<dc.ApplicationStatus> status, dc.EnumValue<dc.ApplicationStatus> status,
) { ) {
if (status is dc.Known<dc.ApplicationStatus>) { if (status is dc.Known<dc.ApplicationStatus>) {
switch (status.value) { 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: case dc.ApplicationStatus.PENDING:
return CoverageWorkerStatus.pending;
case dc.ApplicationStatus.ACCEPTED:
return CoverageWorkerStatus.confirmed;
case dc.ApplicationStatus.REJECTED: 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: 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) { String? _formatTime(fdc.Timestamp? timestamp) {

View File

@@ -1,4 +1,4 @@
import '../ui_entities/coverage_entities.dart'; import 'package:krow_domain/krow_domain.dart';
/// Repository interface for coverage-related operations. /// Repository interface for coverage-related operations.
/// ///

View File

@@ -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,
];
}

View File

@@ -1,7 +1,8 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../arguments/get_coverage_stats_arguments.dart'; import '../arguments/get_coverage_stats_arguments.dart';
import '../repositories/coverage_repository.dart'; import '../repositories/coverage_repository.dart';
import '../ui_entities/coverage_entities.dart';
/// Use case for fetching coverage statistics for a specific date. /// Use case for fetching coverage statistics for a specific date.
/// ///

View File

@@ -1,7 +1,7 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import '../arguments/get_shifts_for_date_arguments.dart'; import '../arguments/get_shifts_for_date_arguments.dart';
import '../repositories/coverage_repository.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. /// Use case for fetching shifts for a specific date.
/// ///

View File

@@ -1,7 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/arguments/get_coverage_stats_arguments.dart'; import '../../domain/arguments/get_coverage_stats_arguments.dart';
import '../../domain/arguments/get_shifts_for_date_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_coverage_stats_usecase.dart';
import '../../domain/usecases/get_shifts_for_date_usecase.dart'; import '../../domain/usecases/get_shifts_for_date_usecase.dart';
import 'coverage_event.dart'; import 'coverage_event.dart';

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; 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 representing the status of coverage data loading.
enum CoverageStatus { enum CoverageStatus {

View File

@@ -1,6 +1,6 @@
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.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. /// Quick statistics cards showing coverage metrics.
/// ///

View File

@@ -1,7 +1,7 @@
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.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. /// List of shifts with their workers.
/// ///
@@ -194,7 +194,8 @@ class _ShiftHeader extends StatelessWidget {
size: UiConstants.space3, size: UiConstants.space3,
color: UiColors.iconSecondary, color: UiColors.iconSecondary,
), ),
Expanded(child: Text( Expanded(
child: Text(
location, location,
style: UiTypography.body3r.textSecondary, style: UiTypography.body3r.textSecondary,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -314,36 +315,92 @@ class _WorkerRow extends StatelessWidget {
Color badgeText; Color badgeText;
String badgeLabel; String badgeLabel;
if (worker.isCheckedIn) { switch (worker.status) {
bg = UiColors.textSuccess.withOpacity(0.1); case CoverageWorkerStatus.checkedIn:
border = UiColors.textSuccess; bg = UiColors.textSuccess.withOpacity(0.1);
textBg = UiColors.textSuccess.withOpacity(0.2); border = UiColors.textSuccess;
textColor = UiColors.textSuccess; textBg = UiColors.textSuccess.withOpacity(0.2);
icon = UiIcons.success; textColor = UiColors.textSuccess;
statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}'; icon = UiIcons.success;
badgeBg = UiColors.textSuccess; statusText = '✓ Checked In at ${formatTime(worker.checkInTime)}';
badgeText = UiColors.primaryForeground; badgeBg = UiColors.textSuccess;
badgeLabel = 'On Site'; badgeText = UiColors.primaryForeground;
} else if (worker.isEnRoute) { badgeLabel = 'On Site';
bg = UiColors.textWarning.withOpacity(0.1); case CoverageWorkerStatus.confirmed:
border = UiColors.textWarning; if (worker.checkInTime == null) {
textBg = UiColors.textWarning.withOpacity(0.2); bg = UiColors.textWarning.withOpacity(0.1);
textColor = UiColors.textWarning; border = UiColors.textWarning;
icon = UiIcons.clock; textBg = UiColors.textWarning.withOpacity(0.2);
statusText = 'En Route - Expected $shiftStartTime'; textColor = UiColors.textWarning;
badgeBg = UiColors.textWarning; icon = UiIcons.clock;
badgeText = UiColors.primaryForeground; statusText = 'En Route - Expected $shiftStartTime';
badgeLabel = 'En Route'; badgeBg = UiColors.textWarning;
} else { badgeText = UiColors.primaryForeground;
bg = UiColors.destructive.withOpacity(0.1); badgeLabel = 'En Route';
border = UiColors.destructive; } else {
textBg = UiColors.destructive.withOpacity(0.2); bg = UiColors.muted.withOpacity(0.1);
textColor = UiColors.destructive; border = UiColors.border;
icon = UiIcons.warning; textBg = UiColors.muted.withOpacity(0.2);
statusText = '⚠ Running Late'; textColor = UiColors.textSecondary;
badgeBg = UiColors.destructive; icon = UiIcons.success;
badgeText = UiColors.destructiveForeground; statusText = 'Confirmed';
badgeLabel = 'Late'; 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( return Container(

View File

@@ -43,14 +43,11 @@ class ReorderWidget extends StatelessWidget {
), ),
if (subtitle != null) ...<Widget>[ if (subtitle != null) ...<Widget>[
const SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Text( Text(subtitle!, style: UiTypography.body2r.textSecondary),
subtitle!,
style: UiTypography.body2r.textSecondary,
),
], ],
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
SizedBox( SizedBox(
height: 140, height: 164,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: recentOrders.length, itemCount: recentOrders.length,
@@ -67,13 +64,7 @@ class ReorderWidget extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border, width: 0.6),
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.black.withValues(alpha: 0.02),
blurRadius: 4,
),
],
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -129,10 +120,7 @@ class ReorderWidget extends StatelessWidget {
style: UiTypography.body1b, style: UiTypography.body1b,
), ),
Text( Text(
i18n.per_hr( '${i18n.per_hr(amount: order.hourlyRate.toString())} · ${order.hours}h',
amount: order.hourlyRate.toString(),
) +
' · ${order.hours}h',
style: UiTypography.footnote2r.textSecondary, style: UiTypography.footnote2r.textSecondary,
), ),
], ],
@@ -145,49 +133,37 @@ class ReorderWidget extends StatelessWidget {
_Badge( _Badge(
icon: UiIcons.success, icon: UiIcons.success,
text: order.type, text: order.type,
color: const Color(0xFF2563EB), color: UiColors.primary,
bg: const Color(0xFF2563EB), bg: UiColors.buttonSecondaryStill,
textColor: UiColors.white, textColor: UiColors.primary,
), ),
const SizedBox(width: UiConstants.space2), const SizedBox(width: UiConstants.space2),
_Badge( _Badge(
icon: UiIcons.building, icon: UiIcons.building,
text: '${order.workers}', text: '${order.workers}',
color: const Color(0xFF334155), color: UiColors.textSecondary,
bg: const Color(0xFFF1F5F9), bg: UiColors.buttonSecondaryStill,
textColor: const Color(0xFF334155), textColor: UiColors.textSecondary,
), ),
], ],
), ),
const Spacer(), const Spacer(),
SizedBox(
height: 28, UiButton.secondary(
width: double.infinity, size: UiButtonSize.small,
child: ElevatedButton.icon( text: i18n.reorder_button,
onPressed: () => onReorderPressed(<String, dynamic>{ leadingIcon: UiIcons.zap,
'orderId': order.orderId, iconSize: 12,
'title': order.title, fullWidth: true,
'location': order.location, onPressed: () => onReorderPressed(<String, dynamic>{
'hourlyRate': order.hourlyRate, 'orderId': order.orderId,
'hours': order.hours, 'title': order.title,
'workers': order.workers, 'location': order.location,
'type': order.type, 'hourlyRate': order.hourlyRate,
}), 'hours': order.hours,
style: ElevatedButton.styleFrom( 'workers': order.workers,
backgroundColor: UiColors.primary, 'type': order.type,
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,
),
),
), ),
], ],
), ),

View File

@@ -35,7 +35,7 @@ class SettingsProfileHeader extends StatelessWidget {
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
background: Container( background: Container(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8), padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8),
margin: const EdgeInsets.only(top: UiConstants.space16), margin: const EdgeInsets.only(top: UiConstants.space24),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,

View File

@@ -1,4 +1,3 @@
import 'dart:ui';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -10,6 +9,7 @@ import '../blocs/view_orders_cubit.dart';
import '../blocs/view_orders_state.dart'; import '../blocs/view_orders_state.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../widgets/view_order_card.dart'; import '../widgets/view_order_card.dart';
import '../widgets/view_orders_header.dart';
import '../navigation/view_orders_navigator.dart'; import '../navigation/view_orders_navigator.dart';
/// The main page for viewing client orders. /// The main page for viewing client orders.
@@ -22,6 +22,7 @@ class ViewOrdersPage extends StatelessWidget {
/// Creates a [ViewOrdersPage]. /// Creates a [ViewOrdersPage].
const ViewOrdersPage({super.key, this.initialDate}); const ViewOrdersPage({super.key, this.initialDate});
/// The initial date to display orders for.
final DateTime? initialDate; final DateTime? initialDate;
@override @override
@@ -37,7 +38,8 @@ class ViewOrdersPage extends StatelessWidget {
class ViewOrdersView extends StatefulWidget { class ViewOrdersView extends StatefulWidget {
/// Creates a [ViewOrdersView]. /// Creates a [ViewOrdersView].
const ViewOrdersView({super.key, this.initialDate}); const ViewOrdersView({super.key, this.initialDate});
/// The initial date to display orders for.
final DateTime? initialDate; final DateTime? initialDate;
@override @override
@@ -88,376 +90,84 @@ class _ViewOrdersViewState extends State<ViewOrdersView> {
} }
return Scaffold( return Scaffold(
body: Stack( body: SafeArea(
children: <Widget>[ child: Column(
// Background Gradient children: <Widget>[
Container( // Header + Filter + Calendar (Sticky behavior)
decoration: const BoxDecoration( ViewOrdersHeader(
gradient: LinearGradient( state: state,
begin: Alignment.topCenter, calendarDays: calendarDays,
end: Alignment.bottomCenter,
colors: <Color>[UiColors.bgSecondary, UiColors.white],
stops: <double>[0.0, 0.3],
),
), ),
),
// Content List
SafeArea( Expanded(
child: Column( child: filteredOrders.isEmpty
children: <Widget>[ ? _buildEmptyState(context: context, state: state)
// Header + Filter + Calendar (Sticky behavior) : ListView(
_buildHeader( padding: const EdgeInsets.fromLTRB(
context: context, UiConstants.space5,
state: state, UiConstants.space4,
calendarDays: calendarDays, UiConstants.space5,
), 100,
),
// Content List children: <Widget>[
Expanded( if (filteredOrders.isNotEmpty)
child: filteredOrders.isEmpty Padding(
? _buildEmptyState(context: context, state: state) padding: const EdgeInsets.only(
: ListView( bottom: UiConstants.space3,
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),
),
), ),
], 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. /// Builds the empty state view.
Widget _buildEmptyState({ Widget _buildEmptyState({
required BuildContext context, required BuildContext context,

View File

@@ -8,6 +8,7 @@ import 'package:intl/intl.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../blocs/view_orders_cubit.dart'; import '../blocs/view_orders_cubit.dart';
/// A rich card displaying details of a client order/shift. /// A rich card displaying details of a client order/shift.
@@ -325,15 +326,23 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
children: <Widget>[ children: <Widget>[
Row( Row(
children: <Widget>[ children: <Widget>[
if (order.workersNeeded != 0) if (coveragePercent != 100)
const Icon( const Icon(
UiIcons.error, UiIcons.error,
size: 16, size: 16,
color: UiColors.textError, color: UiColors.textError,
), ),
if (coveragePercent == 100)
const Icon(
UiIcons.checkCircle,
size: 16,
color: UiColors.textSuccess,
),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'${order.workersNeeded} Workers Needed', coveragePercent == 100
? 'All Workers Confirmed'
: '${order.workersNeeded} Workers Needed',
style: UiTypography.body2m.textPrimary, style: UiTypography.body2m.textPrimary,
), ),
], ],

View File

@@ -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),
],
),
);
}
}

View File

@@ -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),
],
),
),
),
);
}
}

View File

@@ -25,10 +25,14 @@ dependencies:
path: ../../../domain path: ../../../domain
krow_core: krow_core:
path: ../../../core path: ../../../core
krow_data_connect:
path: ../../../data_connect
# UI # UI
lucide_icons: ^0.257.0 lucide_icons: ^0.257.0
intl: ^0.20.1 intl: ^0.20.1
url_launcher: ^6.3.1 url_launcher: ^6.3.1
firebase_data_connect: ^0.2.2+2
firebase_auth: ^6.1.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -4,6 +4,7 @@ import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart' as domain; import 'package:krow_domain/krow_domain.dart' as domain;
import '../../utils/test_phone_numbers.dart';
import '../../domain/ui_entities/auth_mode.dart'; import '../../domain/ui_entities/auth_mode.dart';
import '../../domain/repositories/auth_repository_interface.dart'; import '../../domain/repositories/auth_repository_interface.dart';
@@ -41,7 +42,17 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
await firebaseAuth.verifyPhoneNumber( await firebaseAuth.verifyPhoneNumber(
phoneNumber: phoneNumber, 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) { verificationFailed: (FirebaseAuthException e) {
if (!completer.isCompleted) { if (!completer.isCompleted) {
@@ -168,7 +179,11 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
avatar: staffRecord.photoUrl, avatar: staffRecord.photoUrl,
); );
StaffSessionStore.instance.setSession( StaffSessionStore.instance.setSession(
StaffSession(user: domainUser, staff: domainStaff), StaffSession(
user: domainUser,
staff: domainStaff,
ownerId: staffRecord?.ownerId,
),
); );
return domainUser; return domainUser;
} }

View File

@@ -60,7 +60,7 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
if (session != null) { if (session != null) {
StaffSessionStore.instance.setSession( StaffSessionStore.instance.setSession(
StaffSession(user: session.user, staff: staff), StaffSession(user: session.user, staff: staff, ownerId: session.ownerId),
); );
} }
} }

View File

@@ -1,15 +1,15 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:design_system/design_system.dart'; import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
import 'package:staff_authentication/src/presentation/blocs/auth_event.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_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 'package:staff_authentication/staff_authentication.dart';
import '../navigation/auth_navigator.dart'; // Import the extension 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. /// A combined page for phone number entry and OTP verification.
/// ///

View File

@@ -7,7 +7,6 @@ class GetStartedBackground extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Colors.white,
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 32), const SizedBox(height: 32),

View File

@@ -9,15 +9,29 @@ import 'phone_input/phone_input_form_field.dart';
import 'phone_input/phone_input_header.dart'; import 'phone_input/phone_input_header.dart';
/// A widget that displays the phone number entry UI. /// 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. /// The current state of the authentication process.
final AuthState state; final AuthState state;
/// Callback for when the "Send Code" action is triggered. /// Callback for when the "Send Code" action is triggered.
final VoidCallback onSendCode; final VoidCallback onSendCode;
/// Creates a [PhoneInput]. @override
const PhoneInput({super.key, required this.state, required this.onSendCode}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -35,19 +49,18 @@ class PhoneInput extends StatelessWidget {
const PhoneInputHeader(), const PhoneInputHeader(),
const SizedBox(height: UiConstants.space8), const SizedBox(height: UiConstants.space8),
PhoneInputFormField( PhoneInputFormField(
initialValue: state.phoneNumber, initialValue: widget.state.phoneNumber,
error: state.errorMessage ?? '', error: widget.state.errorMessage ?? '',
onChanged: (String value) { onChanged: _handlePhoneChanged,
BlocProvider.of<AuthBloc>(
context,
).add(AuthPhoneUpdated(value));
},
), ),
], ],
), ),
), ),
), ),
PhoneInputActions(isLoading: state.isLoading, onSendCode: onSendCode), PhoneInputActions(
isLoading: widget.state.isLoading,
onSendCode: widget.onSendCode,
),
], ],
); );
} }

View File

@@ -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);
}
}

View File

@@ -1,128 +1,197 @@
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; 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'; import '../../domain/repositories/clock_in_repository_interface.dart';
/// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect. /// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect.
class ClockInRepositoryImpl implements ClockInRepositoryInterface { class ClockInRepositoryImpl implements ClockInRepositoryInterface {
final dc.ExampleConnector _dataConnect; final dc.ExampleConnector _dataConnect;
final firebase.FirebaseAuth _firebaseAuth; final Map<String, String> _shiftToApplicationId = {};
ClockInRepositoryImpl({ ClockInRepositoryImpl({
required dc.ExampleConnector dataConnect, required dc.ExampleConnector dataConnect,
required firebase.FirebaseAuth firebaseAuth, }) : _dataConnect = dataConnect;
}) : _dataConnect = dataConnect,
_firebaseAuth = firebaseAuth;
Future<String> _getStaffId() async { Future<String> _getStaffId() async {
final firebase.User? user = _firebaseAuth.currentUser; final StaffSession? session = StaffSessionStore.instance.session;
if (user == null) throw Exception('User not authenticated'); final String? staffId = session?.staff?.id;
if (staffId != null && staffId.isNotEmpty) {
final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> result = return staffId;
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
if (result.data.staffs.isEmpty) {
throw Exception('Staff profile not found');
} }
return result.data.staffs.first.id; throw Exception('Staff session not found');
} }
/// Helper to convert Data Connect Timestamp to DateTime /// Helper to convert Data Connect Timestamp to DateTime
DateTime? _toDateTime(dynamic t) { DateTime? _toDateTime(dynamic t) {
if (t == null) return null; if (t == null) return null;
// Attempt to use toJson assuming it matches the generated code's expectation of String DateTime? dt;
try { if (t is DateTime) {
// If t has toDate (e.g. cloud_firestore), usage would be t.toDate() dt = t;
// But here we rely on toJson or toString } else if (t is String) {
return DateTime.tryParse(t.toJson() as String); dt = DateTime.tryParse(t);
} catch (_) { } else {
try { try {
return DateTime.tryParse(t.toString()); if (t is Timestamp) {
} catch (e) { dt = t.toDateTime();
return null; }
} } 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 /// Helper to create Timestamp from DateTime
Timestamp _fromDateTime(DateTime d) { Timestamp _fromDateTime(DateTime d) {
// Assuming Timestamp.fromJson takes an ISO string // 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 ({Timestamp start, Timestamp end}) _utcDayRange(DateTime localDay) {
Future<dc.GetApplicationsByStaffIdApplications?> _getTodaysApplication(String staffId) async { 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(); final DateTime now = DateTime.now();
final range = _utcDayRange(now);
// Fetch recent applications (assuming meaningful limit) final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables>
final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> result = result = await _dataConnect
await _dataConnect.getApplicationsByStaffId( .getApplicationsByStaffId(staffId: staffId)
staffId: staffId, .dayStart(range.start)
).limit(20).execute(); .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 { try {
return result.data.applications.firstWhere((dc.GetApplicationsByStaffIdApplications app) { return apps.firstWhere((app) {
final DateTime? shiftTime = _toDateTime(app.shift.startTime); final status = app.status.stringValue;
return status == 'CHECKED_IN' || status == 'LATE';
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';
}); });
} catch (e) { } catch (_) {
return null; return null;
} }
} }
@override @override
Future<Shift?> getTodaysShift() async { Future<List<Shift>> getTodaysShifts() async {
final String staffId = await _getStaffId(); 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 String roleName = app.shiftRole.role.name;
final String orderName =
final QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> shiftResult = (shift.order.eventName ?? '').trim().isNotEmpty
await _dataConnect.getShiftById(id: shift.id).execute(); ? shift.order.eventName!
: shift.order.business.businessName;
if (shiftResult.data.shift == null) return null; final String title = '$roleName - $orderName';
shifts.add(
final dc.GetShiftByIdShift fullShift = shiftResult.data.shift!; Shift(
id: shift.id,
return Shift( title: title,
id: fullShift.id, clientName: shift.order.business.businessName,
title: fullShift.title, logoUrl: shift.order.business.companyLogoUrl ?? '',
clientName: fullShift.order.business.businessName, hourlyRate: app.shiftRole.role.costPerHour,
logoUrl: '', // Not available in GetShiftById location: shift.location ?? '',
hourlyRate: 0.0, locationAddress: shift.order.teamHub.hubName,
location: fullShift.location ?? '', date: startDt?.toIso8601String() ?? '',
locationAddress: fullShift.locationAddress ?? '', startTime: startDt?.toIso8601String() ?? '',
date: _toDateTime(fullShift.startTime)?.toIso8601String() ?? '', endTime: endDt?.toIso8601String() ?? '',
startTime: _toDateTime(fullShift.startTime)?.toIso8601String() ?? '', createdDate: createdDt?.toIso8601String() ?? '',
endTime: _toDateTime(fullShift.endTime)?.toIso8601String() ?? '', status: shift.status?.stringValue,
createdDate: _toDateTime(fullShift.createdAt)?.toIso8601String() ?? '', description: shift.description,
status: fullShift.status?.stringValue, latitude: shift.latitude,
description: fullShift.description, longitude: shift.longitude,
); ),
);
}
return shifts;
} }
@override @override
Future<AttendanceStatus> getAttendanceStatus() async { Future<AttendanceStatus> getAttendanceStatus() async {
final String staffId = await _getStaffId(); final String staffId = await _getStaffId();
final dc.GetApplicationsByStaffIdApplications? app = await _getTodaysApplication(staffId); final List<dc.GetApplicationsByStaffIdApplications> apps =
await _getTodaysApplications(staffId);
if (app == null) { if (apps.isEmpty) {
return const AttendanceStatus(isCheckedIn: false); return const AttendanceStatus(isCheckedIn: false);
} }
final dc.GetApplicationsByStaffIdApplications? activeApp =
_getActiveApplication(apps);
final dc.GetApplicationsByStaffIdApplications app =
activeApp ?? apps.last;
return ClockInAdapter.toAttendanceStatus( return ClockInAdapter.toAttendanceStatus(
status: app.status.stringValue, status: app.status.stringValue,
checkInTime: _toDateTime(app.checkInTime), checkInTime: _toDateTime(app.checkInTime),
@@ -134,19 +203,25 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
@override @override
Future<AttendanceStatus> clockIn({required String shiftId, String? notes}) async { Future<AttendanceStatus> clockIn({required String shiftId, String? notes}) async {
final String staffId = await _getStaffId(); final String staffId = await _getStaffId();
final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> appsResult = final String? cachedAppId = _shiftToApplicationId[shiftId];
await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute(); dc.GetApplicationsByStaffIdApplications? app;
if (cachedAppId != null) {
final dc.GetApplicationsByStaffIdApplications app = appsResult.data.applications.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId); try {
final apps = await _getTodaysApplications(staffId);
await _dataConnect.updateApplicationStatus( app = apps.firstWhere((a) => a.id == cachedAppId);
id: app.id, } catch (_) {}
roleId: app.shiftRole.id, }
) app ??= (await _getTodaysApplications(staffId))
.status(dc.ApplicationStatus.CHECKED_IN) .firstWhere((a) => a.shiftId == shiftId);
.checkInTime(_fromDateTime(DateTime.now()))
.execute(); await _dataConnect
.updateApplicationStatus(
id: app.id,
roleId: app.shiftRole.id,
)
.checkInTime(_fromDateTime(DateTime.now()))
.execute();
return getAttendanceStatus(); return getAttendanceStatus();
} }
@@ -155,7 +230,10 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}) async { Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}) async {
final String staffId = await _getStaffId(); 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'); if (app == null) throw Exception('No active shift found to clock out');
await _dataConnect.updateApplicationStatus( await _dataConnect.updateApplicationStatus(
@@ -168,10 +246,4 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
return getAttendanceStatus(); 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>>[];
}
} }

View File

@@ -3,9 +3,9 @@ import 'package:krow_domain/krow_domain.dart';
/// Repository interface for Clock In/Out functionality /// Repository interface for Clock In/Out functionality
abstract class ClockInRepositoryInterface { abstract class ClockInRepositoryInterface {
/// Retrieves the shift assigned to the user for the current day. /// Retrieves the shifts assigned to the user for the current day.
/// Returns null if no shift is assigned for today. /// Returns empty list if no shift is assigned for today.
Future<Shift?> getTodaysShift(); Future<List<Shift>> getTodaysShifts();
/// Gets the current attendance status (e.g., checked in or not, times). /// Gets the current attendance status (e.g., checked in or not, times).
/// This helps in restoring the UI state if the app was killed. /// 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. /// Checks the user out for the currently active shift.
/// Optionally accepts [breakTimeMinutes] if tracked. /// Optionally accepts [breakTimeMinutes] if tracked.
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}); Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes});
/// Retrieves a list of recent clock-in/out activities.
Future<List<Map<String, dynamic>>> getActivityLog();
} }

View File

@@ -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();
}
}

View File

@@ -2,14 +2,14 @@ import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../repositories/clock_in_repository_interface.dart'; import '../repositories/clock_in_repository_interface.dart';
/// Use case for retrieving the user's scheduled shift for today. /// Use case for retrieving the user's scheduled shifts for today.
class GetTodaysShiftUseCase implements NoInputUseCase<Shift?> { class GetTodaysShiftUseCase implements NoInputUseCase<List<Shift>> {
final ClockInRepositoryInterface _repository; final ClockInRepositoryInterface _repository;
GetTodaysShiftUseCase(this._repository); GetTodaysShiftUseCase(this._repository);
@override @override
Future<Shift?> call() { Future<List<Shift>> call() {
return _repository.getTodaysShift(); return _repository.getTodaysShifts();
} }
} }

View File

@@ -1,10 +1,10 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:geolocator/geolocator.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_todays_shift_usecase.dart';
import '../../domain/usecases/get_attendance_status_usecase.dart'; import '../../domain/usecases/get_attendance_status_usecase.dart';
import '../../domain/usecases/clock_in_usecase.dart'; import '../../domain/usecases/clock_in_usecase.dart';
import '../../domain/usecases/clock_out_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_in_arguments.dart';
import '../../domain/arguments/clock_out_arguments.dart'; import '../../domain/arguments/clock_out_arguments.dart';
import 'clock_in_event.dart'; import 'clock_in_event.dart';
@@ -15,11 +15,8 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
final GetAttendanceStatusUseCase _getAttendanceStatus; final GetAttendanceStatusUseCase _getAttendanceStatus;
final ClockInUseCase _clockIn; final ClockInUseCase _clockIn;
final ClockOutUseCase _clockOut; final ClockOutUseCase _clockOut;
final GetActivityLogUseCase _getActivityLog;
// Mock Venue Location (e.g., Grand Hotel, NYC) // 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; static const double allowedRadiusMeters = 500;
ClockInBloc({ ClockInBloc({
@@ -27,14 +24,13 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
required GetAttendanceStatusUseCase getAttendanceStatus, required GetAttendanceStatusUseCase getAttendanceStatus,
required ClockInUseCase clockIn, required ClockInUseCase clockIn,
required ClockOutUseCase clockOut, required ClockOutUseCase clockOut,
required GetActivityLogUseCase getActivityLog,
}) : _getTodaysShift = getTodaysShift, }) : _getTodaysShift = getTodaysShift,
_getAttendanceStatus = getAttendanceStatus, _getAttendanceStatus = getAttendanceStatus,
_clockIn = clockIn, _clockIn = clockIn,
_clockOut = clockOut, _clockOut = clockOut,
_getActivityLog = getActivityLog,
super(ClockInState(selectedDate: DateTime.now())) { super(ClockInState(selectedDate: DateTime.now())) {
on<ClockInPageLoaded>(_onLoaded); on<ClockInPageLoaded>(_onLoaded);
on<ShiftSelected>(_onShiftSelected);
on<DateSelected>(_onDateSelected); on<DateSelected>(_onDateSelected);
on<CheckInRequested>(_onCheckIn); on<CheckInRequested>(_onCheckIn);
on<CheckOutRequested>(_onCheckOut); on<CheckOutRequested>(_onCheckOut);
@@ -52,21 +48,30 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
) async { ) async {
emit(state.copyWith(status: ClockInStatus.loading)); emit(state.copyWith(status: ClockInStatus.loading));
try { try {
final shift = await _getTodaysShift(); final shifts = await _getTodaysShift();
final status = await _getAttendanceStatus(); final status = await _getAttendanceStatus();
final activity = await _getActivityLog();
// Check permissions silently on load? Maybe better to wait for user interaction or specific event // 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 // 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( emit(state.copyWith(
status: ClockInStatus.success, status: ClockInStatus.success,
todayShift: shift, todayShifts: shifts,
selectedShift: selectedShift,
attendance: status, attendance: status,
activityLog: activity,
)); ));
if (shift != null && !status.isCheckedIn) { if (selectedShift != null && !status.isCheckedIn) {
add(RequestLocationPermission()); add(RequestLocationPermission());
} }
@@ -103,13 +108,25 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
void _startLocationUpdates() async { void _startLocationUpdates() async {
try { try {
final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high); final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
final distance = Geolocator.distanceBetween(
position.latitude, double distance = 0;
position.longitude, bool isVerified = false; // Require location match by default if shift has location
venueLat,
venueLng, if (state.selectedShift != null &&
); state.selectedShift!.latitude != null &&
final isVerified = distance <= allowedRadiusMeters; 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) { if (!isClosed) {
add(LocationUpdated(position: position, distance: distance, isVerified: isVerified)); 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( void _onDateSelected(
DateSelected event, DateSelected event,
Emitter<ClockInState> emit, Emitter<ClockInState> emit,

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:krow_domain/krow_domain.dart';
abstract class ClockInEvent extends Equatable { abstract class ClockInEvent extends Equatable {
const ClockInEvent(); const ClockInEvent();
@@ -10,6 +11,14 @@ abstract class ClockInEvent extends Equatable {
class ClockInPageLoaded extends ClockInEvent {} 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 { class DateSelected extends ClockInEvent {
final DateTime date; final DateTime date;

View File

@@ -7,9 +7,9 @@ enum ClockInStatus { initial, loading, success, failure, actionInProgress }
class ClockInState extends Equatable { class ClockInState extends Equatable {
final ClockInStatus status; final ClockInStatus status;
final Shift? todayShift; final List<Shift> todayShifts;
final Shift? selectedShift;
final AttendanceStatus attendance; final AttendanceStatus attendance;
final List<Map<String, dynamic>> activityLog;
final DateTime selectedDate; final DateTime selectedDate;
final String checkInMode; final String checkInMode;
final String? errorMessage; final String? errorMessage;
@@ -23,9 +23,9 @@ class ClockInState extends Equatable {
const ClockInState({ const ClockInState({
this.status = ClockInStatus.initial, this.status = ClockInStatus.initial,
this.todayShift, this.todayShifts = const [],
this.selectedShift,
this.attendance = const AttendanceStatus(), this.attendance = const AttendanceStatus(),
this.activityLog = const [],
required this.selectedDate, required this.selectedDate,
this.checkInMode = 'swipe', this.checkInMode = 'swipe',
this.errorMessage, this.errorMessage,
@@ -39,9 +39,9 @@ class ClockInState extends Equatable {
ClockInState copyWith({ ClockInState copyWith({
ClockInStatus? status, ClockInStatus? status,
Shift? todayShift, List<Shift>? todayShifts,
Shift? selectedShift,
AttendanceStatus? attendance, AttendanceStatus? attendance,
List<Map<String, dynamic>>? activityLog,
DateTime? selectedDate, DateTime? selectedDate,
String? checkInMode, String? checkInMode,
String? errorMessage, String? errorMessage,
@@ -54,9 +54,9 @@ class ClockInState extends Equatable {
}) { }) {
return ClockInState( return ClockInState(
status: status ?? this.status, status: status ?? this.status,
todayShift: todayShift ?? this.todayShift, todayShifts: todayShifts ?? this.todayShifts,
selectedShift: selectedShift ?? this.selectedShift,
attendance: attendance ?? this.attendance, attendance: attendance ?? this.attendance,
activityLog: activityLog ?? this.activityLog,
selectedDate: selectedDate ?? this.selectedDate, selectedDate: selectedDate ?? this.selectedDate,
checkInMode: checkInMode ?? this.checkInMode, checkInMode: checkInMode ?? this.checkInMode,
errorMessage: errorMessage, errorMessage: errorMessage,
@@ -72,9 +72,9 @@ class ClockInState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
status, status,
todayShift, todayShifts,
selectedShift,
attendance, attendance,
activityLog,
selectedDate, selectedDate,
checkInMode, checkInMode,
errorMessage, errorMessage,

View File

@@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:lucide_icons/lucide_icons.dart'; import 'package:lucide_icons/lucide_icons.dart';
import '../bloc/clock_in_bloc.dart'; import '../bloc/clock_in_bloc.dart';
import '../bloc/clock_in_event.dart'; import '../bloc/clock_in_event.dart';
import '../bloc/clock_in_state.dart'; import '../bloc/clock_in_state.dart';
import '../theme/app_colors.dart'; import '../theme/app_colors.dart';
import '../widgets/attendance_card.dart';
import '../widgets/commute_tracker.dart'; import '../widgets/commute_tracker.dart';
import '../widgets/date_selector.dart'; import '../widgets/date_selector.dart';
import '../widgets/lunch_break_modal.dart'; import '../widgets/lunch_break_modal.dart';
@@ -46,16 +46,23 @@ class _ClockInPageState extends State<ClockInPage> {
}, },
builder: (context, state) { builder: (context, state) {
if (state.status == ClockInStatus.loading && if (state.status == ClockInStatus.loading &&
state.todayShift == null) { state.todayShifts.isEmpty) {
return const Scaffold( return const Scaffold(
body: Center(child: CircularProgressIndicator()), body: Center(child: CircularProgressIndicator()),
); );
} }
final todayShift = state.todayShift; final todayShifts = state.todayShifts;
final checkInTime = state.attendance.checkInTime; final selectedShift = state.selectedShift;
final checkOutTime = state.attendance.checkOutTime; final activeShiftId = state.attendance.activeShiftId;
final isCheckedIn = state.attendance.isCheckedIn; 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 // Format times for display
final checkInStr = checkInTime != null final checkInStr = checkInTime != null
@@ -89,9 +96,9 @@ class _ClockInPageState extends State<ClockInPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Commute Tracker (shows before date selector when applicable) // Commute Tracker (shows before date selector when applicable)
if (todayShift != null) if (selectedShift != null)
CommuteTracker( CommuteTracker(
shift: todayShift, shift: selectedShift,
hasLocationConsent: state.hasLocationConsent, hasLocationConsent: state.hasLocationConsent,
isCommuteModeOn: state.isCommuteModeOn, isCommuteModeOn: state.isCommuteModeOn,
distanceMeters: state.distanceFromVenue, distanceMeters: state.distanceFromVenue,
@@ -125,92 +132,113 @@ class _ClockInPageState extends State<ClockInPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Selected Shift Info Card // Selected Shift Info Card
if (todayShift != null) if (todayShifts.isNotEmpty)
Container( Column(
padding: const EdgeInsets.all(12), children: todayShifts
margin: const EdgeInsets.only(bottom: 16), .map(
decoration: BoxDecoration( (shift) => GestureDetector(
color: Colors.white, onTap: () =>
borderRadius: BorderRadius.circular(12), _bloc.add(ShiftSelected(shift)),
border: Border.all( child: Container(
color: const Color(0xFFE2E8F0), padding: const EdgeInsets.all(12),
), // slate-200 margin:
boxShadow: [ const EdgeInsets.only(bottom: 12),
BoxShadow( decoration: BoxDecoration(
color: Colors.black.withOpacity(0.05), color: Colors.white,
blurRadius: 2, borderRadius: BorderRadius.circular(
offset: const Offset(0, 1), 12,
), ),
], border: Border.all(
), color: shift.id ==
child: Row( selectedShift?.id
crossAxisAlignment: CrossAxisAlignment.start, ? AppColors.krowBlue
children: [ : const Color(0xFFE2E8F0),
Expanded( width:
child: Column( shift.id == selectedShift?.id
crossAxisAlignment: ? 2
CrossAxisAlignment.start, : 1,
children: [
const Text(
"TODAY'S SHIFT",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: AppColors.krowBlue,
letterSpacing: 0.5,
), ),
), ),
const SizedBox(height: 2), child: Row(
Text( crossAxisAlignment:
todayShift.title, CrossAxisAlignment.start,
style: const TextStyle( children: [
fontSize: 14, Expanded(
fontWeight: FontWeight.w600, child: Column(
color: Color( crossAxisAlignment:
0xFF1E293B, CrossAxisAlignment.start,
), // slate-800 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( .toList(),
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,
),
),
],
),
],
),
), ),
// Swipe To Check In / Checked Out State / No Shift State // Swipe To Check In / Checked Out State / No Shift State
if (todayShift != null && checkOutTime == null) ...[ if (selectedShift != null && checkOutTime == null) ...[
if (!isCheckedIn && !_isCheckInAllowed(todayShift)) if (!isCheckedIn &&
!_isCheckInAllowed(selectedShift))
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
@@ -236,7 +264,7 @@ class _ClockInPageState extends State<ClockInPage> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
"Check-in available at ${_getCheckInAvailabilityTime(todayShift)}", "Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}",
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF64748B), // slate-500 color: Color(0xFF64748B), // slate-500
@@ -259,7 +287,9 @@ class _ClockInPageState extends State<ClockInPage> {
await _showNFCDialog(context); await _showNFCDialog(context);
} else { } else {
_bloc.add( _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) ...[ checkOutTime != null) ...[
// Shift Completed State // Shift Completed State
Container( Container(
@@ -417,78 +447,7 @@ class _ClockInPageState extends State<ClockInPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Recent Activity List // Recent Activity List (Temporarily removed)
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,
),
),
],
),
),
),
const SizedBox(height: 16), 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 { Future<void> _showNFCDialog(BuildContext context) async {
bool scanned = false; bool scanned = false;
@@ -731,31 +609,40 @@ class _ClockInPageState extends State<ClockInPage> {
// After dialog closes, trigger the event if scan was successful (simulated) // After dialog closes, trigger the event if scan was successful (simulated)
// In real app, we would check the dialog result // In real app, we would check the dialog result
if (scanned && _bloc.state.todayShift != null) { if (scanned && _bloc.state.selectedShift != null) {
_bloc.add(CheckInRequested(shiftId: _bloc.state.todayShift!.id)); _bloc.add(CheckInRequested(shiftId: _bloc.state.selectedShift!.id));
} }
} }
// --- Helper Methods --- // --- Helper Methods ---
String _formatTime(String timeStr) { String _formatTime(String timeStr) {
// Expecting HH:mm or HH:mm:ss if (timeStr.isEmpty) return '';
try { try {
if (timeStr.isEmpty) return ''; // Try parsing as ISO string first (which contains date)
final parts = timeStr.split(':'); final dt = DateTime.parse(timeStr);
final dt = DateTime(2022, 1, 1, int.parse(parts[0]), int.parse(parts[1]));
return DateFormat('h:mm a').format(dt); return DateFormat('h:mm a').format(dt);
} catch (e) { } catch (_) {
return timeStr; // 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; if (shift == null) return false;
try { try {
// Parse shift date (e.g. 2024-01-31T09:00:00) // Parse shift date (e.g. 2024-01-31T09:00:00)
// The Shift entity has 'date' which is the start DateTime string // 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)); final windowStart = shiftStart.subtract(const Duration(minutes: 15));
return DateTime.now().isAfter(windowStart); return DateTime.now().isAfter(windowStart);
} catch (e) { } catch (e) {
@@ -764,10 +651,10 @@ class _ClockInPageState extends State<ClockInPage> {
} }
} }
String _getCheckInAvailabilityTime(dynamic shift) { String _getCheckInAvailabilityTime(Shift shift) {
if (shift == null) return ''; if (shift == null) return '';
try { try {
final shiftStart = DateTime.parse(shift.date); final shiftStart = DateTime.parse(shift.startTime.trim());
final windowStart = shiftStart.subtract(const Duration(minutes: 15)); final windowStart = shiftStart.subtract(const Duration(minutes: 15));
return DateFormat('h:mm a').format(windowStart); return DateFormat('h:mm a').format(windowStart);
} catch (e) { } catch (e) {

View File

@@ -66,9 +66,14 @@ class _CommuteTrackerState extends State<CommuteTracker> {
// For demo purposes, check if we're within 24 hours of shift // For demo purposes, check if we're within 24 hours of shift
final now = DateTime.now(); final now = DateTime.now();
final shiftStart = DateTime.parse( DateTime shiftStart;
'${widget.shift!.date} ${widget.shift!.startTime}', try {
); shiftStart = DateTime.parse(widget.shift!.startTime);
} catch (_) {
shiftStart = DateTime.parse(
'${widget.shift!.date} ${widget.shift!.startTime}',
);
}
final hoursUntilShift = shiftStart.difference(now).inHours; final hoursUntilShift = shiftStart.difference(now).inHours;
final inCommuteWindow = hoursUntilShift <= 24 && hoursUntilShift >= 0; final inCommuteWindow = hoursUntilShift <= 24 && hoursUntilShift >= 0;

View File

@@ -1,4 +1,3 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.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/repositories/clock_in_repository_interface.dart';
import 'domain/usecases/clock_in_usecase.dart'; import 'domain/usecases/clock_in_usecase.dart';
import 'domain/usecases/clock_out_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_attendance_status_usecase.dart';
import 'domain/usecases/get_todays_shift_usecase.dart'; import 'domain/usecases/get_todays_shift_usecase.dart';
import 'presentation/bloc/clock_in_bloc.dart'; import 'presentation/bloc/clock_in_bloc.dart';
@@ -19,7 +17,6 @@ class StaffClockInModule extends Module {
i.add<ClockInRepositoryInterface>( i.add<ClockInRepositoryInterface>(
() => ClockInRepositoryImpl( () => ClockInRepositoryImpl(
dataConnect: ExampleConnector.instance, dataConnect: ExampleConnector.instance,
firebaseAuth: FirebaseAuth.instance,
), ),
); );
@@ -28,7 +25,6 @@ class StaffClockInModule extends Module {
i.add<GetAttendanceStatusUseCase>(GetAttendanceStatusUseCase.new); i.add<GetAttendanceStatusUseCase>(GetAttendanceStatusUseCase.new);
i.add<ClockInUseCase>(ClockInUseCase.new); i.add<ClockInUseCase>(ClockInUseCase.new);
i.add<ClockOutUseCase>(ClockOutUseCase.new); i.add<ClockOutUseCase>(ClockOutUseCase.new);
i.add<GetActivityLogUseCase>(GetActivityLogUseCase.new);
// BLoC // BLoC
i.add<ClockInBloc>(ClockInBloc.new); i.add<ClockInBloc>(ClockInBloc.new);

View File

@@ -32,28 +32,40 @@ class HomeRepositoryImpl implements HomeRepository {
Future<List<Shift>> _getShiftsForDate(DateTime date) async { Future<List<Shift>> _getShiftsForDate(DateTime date) async {
try { 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 final response = await ExampleConnector.instance
.getApplicationsByStaffId(staffId: _currentStaffId) .getApplicationsByStaffId(staffId: staffId)
.dayStart(_toTimestamp(start))
.dayEnd(_toTimestamp(end))
.execute(); .execute();
final targetYmd = DateFormat('yyyy-MM-dd').format(date); // Filter for ACCEPTED applications (same logic as shifts_repository_impl)
final apps = response.data.applications.where(
return response.data.applications (app) => (app.status is Known && (app.status as Known).value == ApplicationStatus.ACCEPTED) || (app.status is Known && (app.status as Known).value == ApplicationStatus.CONFIRMED)
.where((app) { );
final shiftDate = app.shift.date?.toDate();
if (shiftDate == null) return false; final List<Shift> shifts = [];
for (final app in apps) {
final isDateMatch = DateFormat('yyyy-MM-dd').format(shiftDate) == targetYmd; shifts.add(_mapApplicationToShift(app));
final isAssigned = app.status is Known && (app.status as Known).value == ApplicationStatus.ACCEPTED; }
return isDateMatch && isAssigned; return shifts;
})
.map((app) => _mapApplicationToShift(app))
.toList();
} catch (e) { } catch (e) {
return []; 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 @override
Future<List<Shift>> getRecommendedShifts() async { Future<List<Shift>> getRecommendedShifts() async {
@@ -93,21 +105,26 @@ class HomeRepositoryImpl implements HomeRepository {
final s = app.shift; final s = app.shift;
final r = app.shiftRole; final r = app.shiftRole;
return Shift( return ShiftAdapter.fromApplicationData(
id: s.id, shiftId: s.id,
title: r.role.name, roleId: r.roleId,
clientName: s.order.business.businessName, roleName: r.role.name,
hourlyRate: r.role.costPerHour, businessName: s.order.business.businessName,
location: s.location ?? 'Unknown', companyLogoUrl: s.order.business.companyLogoUrl,
locationAddress: s.location ?? '', costPerHour: r.role.costPerHour,
date: s.date?.toDate().toIso8601String() ?? '', shiftLocation: s.location,
startTime: DateFormat('HH:mm').format(r.startTime?.toDate() ?? DateTime.now()), teamHubName: s.order.teamHub.hubName,
endTime: DateFormat('HH:mm').format(r.endTime?.toDate() ?? DateTime.now()), shiftDate: s.date?.toDate(),
createdDate: app.createdAt?.toDate().toIso8601String() ?? '', startTime: r.startTime?.toDate(),
tipsAvailable: false, // Not in API endTime: r.endTime?.toDate(),
mealProvided: false, // Not in API createdAt: app.createdAt?.toDate(),
managers: [], // Not in this query status: 'confirmed',
description: null, description: s.description,
durationDays: s.durationDays,
count: r.count,
assigned: r.assigned,
eventName: s.order.eventName,
hasApplied: true,
); );
} }

View File

@@ -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/recommended_shift_card.dart';
import 'package:staff_home/src/presentation/widgets/home_page/section_header.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/shift_card.dart';
import 'package:staff_home/src/presentation/widgets/worker/auto_match_toggle.dart';
/// The home page for the staff worker application. /// The home page for the staff worker application.
/// ///

View File

@@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../navigation/home_navigator.dart';
class ShiftCard extends StatefulWidget { class ShiftCard extends StatefulWidget {
final Shift shift; final Shift shift;
@@ -73,10 +74,7 @@ class _ShiftCardState extends State<ShiftCard> {
? null ? null
: () { : () {
setState(() => isExpanded = !isExpanded); setState(() => isExpanded = !isExpanded);
Modular.to.pushNamed( Modular.to.pushShiftDetails(widget.shift);
'/shift-details/${widget.shift.id}',
arguments: widget.shift,
);
}, },
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),

View File

@@ -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_data_connect/src/session/staff_session_store.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:krow_core/core.dart';
import '../../domain/repositories/payments_repository.dart'; import '../../domain/repositories/payments_repository.dart';
class PaymentsRepositoryImpl implements PaymentsRepository { class PaymentsRepositoryImpl implements PaymentsRepository {
@@ -46,25 +47,34 @@ class PaymentsRepositoryImpl implements PaymentsRepository {
/// Helper to convert Data Connect Timestamp to DateTime /// Helper to convert Data Connect Timestamp to DateTime
DateTime? _toDateTime(dynamic t) { DateTime? _toDateTime(dynamic t) {
if (t == null) return null; if (t == null) return null;
if (t is DateTime) return t; DateTime? dt;
if (t is String) return DateTime.tryParse(t); 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 { try {
if (t is Timestamp) { if (dt == null && t.runtimeType.toString().contains('Timestamp')) {
return t.toDateTime(); dt = (t as dynamic).toDate();
} }
} catch (_) {} } 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) {
dt = DateTime.tryParse(t.toString());
}
} catch (_) {}
}
if (dt != null) {
return DateTimeUtils.toDeviceTime(dt);
}
return null; return null;
} }

View File

@@ -2,6 +2,7 @@ import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart' as domain; import 'package:krow_domain/krow_domain.dart' as domain;
import 'package:krow_core/core.dart';
import '../../domain/repositories/certificates_repository.dart'; import '../../domain/repositories/certificates_repository.dart';
@@ -63,7 +64,9 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
description: null, // Description not available in this query response description: null, // Description not available in this query response
status: _mapStatus(doc.status), status: _mapStatus(doc.status),
documentUrl: doc.documentUrl, documentUrl: doc.documentUrl,
expiryDate: doc.expiryDate?.toDateTime(), expiryDate: doc.expiryDate == null
? null
: DateTimeUtils.toDeviceTime(doc.expiryDate!.toDateTime()),
); );
} }

View File

@@ -2,6 +2,7 @@ import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart' as domain; import 'package:krow_domain/krow_domain.dart' as domain;
import 'package:krow_core/core.dart';
import '../../domain/repositories/documents_repository.dart'; import '../../domain/repositories/documents_repository.dart';
@@ -75,7 +76,9 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
description: null, // Description not available in data source description: null, // Description not available in data source
status: _mapStatus(doc.status), status: _mapStatus(doc.status),
documentUrl: doc.documentUrl, documentUrl: doc.documentUrl,
expiryDate: doc.expiryDate?.toDateTime(), expiryDate: doc.expiryDate == null
? null
: DateTimeUtils.toDeviceTime(doc.expiryDate!.toDateTime()),
); );
} }

View File

@@ -1,4 +1,5 @@
import 'package:firebase_data_connect/firebase_data_connect.dart'; 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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -62,14 +63,17 @@ class TaxFormMapper {
status: form.status.stringValue, status: form.status.stringValue,
staffId: form.staffId, staffId: form.staffId,
formData: formData, formData: formData,
updatedAt: form.updatedAt?.toDateTime(), updatedAt: form.updatedAt == null
? null
: DateTimeUtils.toDeviceTime(form.updatedAt!.toDateTime()),
); );
} }
static String? _formatDate(Timestamp? timestamp) { static String? _formatDate(Timestamp? timestamp) {
if (timestamp == null) return null; 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}'; return '${date.month.toString().padLeft(2, '0')}/${date.day.toString().padLeft(2, '0')}/${date.year}';
} }

View File

@@ -5,6 +5,7 @@ import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart'; import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart';
import 'package:krow_core/core.dart';
import '../../domain/repositories/time_card_repository.dart'; import '../../domain/repositories/time_card_repository.dart';
/// Implementation of [TimeCardRepository] using Firebase Data Connect. /// Implementation of [TimeCardRepository] using Firebase Data Connect.
@@ -40,12 +41,15 @@ class TimeCardRepositoryImpl implements TimeCardRepository {
return result.data.applications return result.data.applications
.where((dc.GetApplicationsByStaffIdApplications app) { .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; if (shiftDate == null) return false;
return shiftDate.year == month.year && shiftDate.month == month.month; return shiftDate.year == month.year && shiftDate.month == month.month;
}) })
.map((dc.GetApplicationsByStaffIdApplications app) { .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 startTime = _formatTime(app.checkInTime) ?? _formatTime(app.shift.startTime) ?? '';
final String endTime = _formatTime(app.checkOutTime) ?? _formatTime(app.shift.endTime) ?? ''; final String endTime = _formatTime(app.checkOutTime) ?? _formatTime(app.shift.endTime) ?? '';
@@ -73,6 +77,7 @@ class TimeCardRepositoryImpl implements TimeCardRepository {
String? _formatTime(fdc.Timestamp? timestamp) { String? _formatTime(fdc.Timestamp? timestamp) {
if (timestamp == null) return null; if (timestamp == null) return null;
return DateFormat('HH:mm').format(timestamp.toDateTime()); return DateFormat('HH:mm')
.format(DateTimeUtils.toDeviceTime(timestamp.toDateTime()));
} }
} }

View File

@@ -37,7 +37,9 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
} }
try { try {
final response = await _dataConnect.getStaffByUserId(userId: user.uid).execute(); final response = await _dataConnect
.getStaffByUserId(userId: user.uid)
.execute();
if (response.data.staffs.isNotEmpty) { if (response.data.staffs.isNotEmpty) {
_cachedStaffId = response.data.staffs.first.id; _cachedStaffId = response.data.staffs.first.id;
return _cachedStaffId!; return _cachedStaffId!;
@@ -47,9 +49,9 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
} }
// 4. Fallback (should ideally not happen if DB is seeded) // 4. Fallback (should ideally not happen if DB is seeded)
return user.uid; return user.uid;
} }
DateTime? _toDateTime(dynamic t) { DateTime? _toDateTime(dynamic t) {
if (t == null) return null; if (t == null) return null;
DateTime? dt; DateTime? dt;
@@ -75,62 +77,76 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
return null; return null;
} }
@override /// Helper method to map Data Connect application to domain Shift using ShiftAdapter.
Future<List<Shift>> getMyShifts() async { Shift _mapApplicationToShift(
return _fetchApplications(dc.ApplicationStatus.ACCEPTED); 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 @override
Future<List<Shift>> getPendingAssignments() async { Future<List<Shift>> getPendingAssignments() async {
return _fetchApplications(dc.ApplicationStatus.PENDING); return _fetchApplications([dc.ApplicationStatus.PENDING]);
} }
@override @override
Future<List<Shift>> getCancelledShifts() async { Future<List<Shift>> getCancelledShifts() async {
return _fetchApplications(dc.ApplicationStatus.REJECTED); return _fetchApplications([dc.ApplicationStatus.REJECTED]);
} }
@override @override
Future<List<Shift>> getHistoryShifts() async { Future<List<Shift>> getHistoryShifts() async {
return _fetchApplications(dc.ApplicationStatus.CHECKED_OUT);
}
Future<List<Shift>> _fetchApplications(dc.ApplicationStatus status) async {
try { try {
final staffId = await _getStaffId(); final staffId = await _getStaffId();
final response = await _dataConnect final response = await _dataConnect
.getApplicationsByStaffId(staffId: staffId) .listCompletedApplicationsByStaffId(staffId: staffId)
.execute(); .execute();
final apps = response.data.applications.where((app) => app.status.stringValue == status.name);
final List<Shift> shifts = []; final List<Shift> shifts = [];
for (final app in apps) { for (final app in response.data.applications) {
_shiftToAppIdMap[app.shift.id] = app.id; _shiftToAppIdMap[app.shift.id] = app.id;
_appToRoleIdMap[app.id] = app.shiftRole.id; _appToRoleIdMap[app.id] = app.shiftRole.id;
final shift = await _getShiftDetails(app.shift.id); shifts.add(
if (shift != null) { _mapApplicationToShift(
// Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED) app,
shifts.add(Shift( _mapStatus(dc.ApplicationStatus.CHECKED_OUT),
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,
));
}
} }
return shifts; return shifts;
} catch (e) { } 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) { String _mapStatus(dc.ApplicationStatus status) {
switch (status) { switch (status) {
case dc.ApplicationStatus.ACCEPTED: case dc.ApplicationStatus.ACCEPTED:
@@ -156,133 +217,301 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
@override @override
Future<List<Shift>> getAvailableShifts(String query, String type) async { Future<List<Shift>> getAvailableShifts(String query, String type) async {
try { try {
final result = await _dataConnect.listShifts().execute(); final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId;
final allShifts = result.data.shifts; if (vendorId == null || vendorId.isEmpty) {
return <Shift>[];
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>[];
} }
}
@override final result = await _dataConnect
Future<Shift?> getShiftDetails(String shiftId) async { .listShiftRolesByVendorId(vendorId: vendorId)
return _getShiftDetails(shiftId); .execute();
} final allShiftRoles = result.data.shiftRoles;
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 startDt = _toDateTime(s.startTime); final List<Shift> mappedShifts = [];
final endDt = _toDateTime(s.endTime); for (final sr in allShiftRoles) {
final createdDt = _toDateTime(s.createdAt); final DateTime? shiftDate = _toDateTime(sr.shift.date);
final startDt = _toDateTime(sr.startTime);
return Shift( final endDt = _toDateTime(sr.endTime);
id: s.id, final createdDt = _toDateTime(sr.createdAt);
title: s.title, mappedShifts.add(
clientName: s.order.business.businessName, Shift(
id: sr.shiftId,
roleId: sr.roleId,
title: sr.role.name,
clientName: sr.shift.order.business.businessName,
logoUrl: null, logoUrl: null,
hourlyRate: s.cost ?? 0.0, hourlyRate: sr.role.costPerHour,
location: s.location ?? '', location: sr.shift.location ?? '',
locationAddress: s.locationAddress ?? '', locationAddress: sr.shift.locationAddress ?? '',
date: startDt?.toIso8601String() ?? '', date: shiftDate?.toIso8601String() ?? '',
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '', startTime: startDt != null
? DateFormat('HH:mm').format(startDt)
: '',
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
createdDate: createdDt?.toIso8601String() ?? '', createdDate: createdDt?.toIso8601String() ?? '',
status: s.status?.stringValue ?? 'OPEN', status: sr.shift.status?.stringValue.toLowerCase() ?? 'open',
description: s.description, description: sr.shift.description,
durationDays: s.durationDays, durationDays: sr.shift.durationDays,
requiredSlots: required, requiredSlots: sr.count,
filledSlots: filled, 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 @override
Future<void> applyForShift(String shiftId, {bool isInstantBook = false}) async { Future<Shift?> getShiftDetails(String shiftId, {String? roleId}) async {
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute(); return _getShiftDetails(shiftId, roleId: roleId);
if (rolesResult.data.shiftRoles.isEmpty) throw Exception('No open roles for this shift'); }
final role = rolesResult.data.shiftRoles.first; Future<Shift?> _getShiftDetails(String shiftId, {String? roleId}) async {
try {
final staffId = await _getStaffId(); if (roleId != null && roleId.isNotEmpty) {
await _dataConnect.createApplication( final roleResult = await _dataConnect
shiftId: shiftId, .getShiftRoleById(shiftId: shiftId, roleId: roleId)
staffId: staffId, .execute();
roleId: role.roleId, final sr = roleResult.data.shiftRole;
status: isInstantBook ? dc.ApplicationStatus.ACCEPTED : dc.ApplicationStatus.PENDING, if (sr == null) return null;
origin: dc.ApplicationOrigin.STAFF,
).execute(); 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 @override
Future<void> acceptShift(String shiftId) async { Future<void> acceptShift(String shiftId) async {
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED); await _updateApplicationStatus(shiftId, dc.ApplicationStatus.CONFIRMED);
}
@override
Future<void> declineShift(String shiftId) async {
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.REJECTED);
} }
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? appId = _shiftToAppIdMap[shiftId];
String? roleId; String? roleId;
@@ -293,44 +522,49 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
// Re-check map // Re-check map
appId = _shiftToAppIdMap[shiftId]; appId = _shiftToAppIdMap[shiftId];
if (appId != null) { if (appId != null) {
roleId = _appToRoleIdMap[appId]; roleId = _appToRoleIdMap[appId];
} else { } else {
// Fallback fetch // Fallback fetch
final staffId = await _getStaffId(); final staffId = await _getStaffId();
final apps = await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute(); final apps = await _dataConnect
final app = apps.data.applications.where((a) => a.shiftId == shiftId).firstOrNull; .getApplicationsByStaffId(staffId: staffId)
if (app != null) { .execute();
appId = app.id; final app = apps.data.applications
roleId = app.shiftRole.id; .where((a) => a.shiftId == shiftId)
} .firstOrNull;
if (app != null) {
appId = app.id;
roleId = app.shiftRole.id;
}
} }
if (appId == null || roleId == null) { if (appId == null || roleId == null) {
// If we are rejecting and can't find an application, create one as rejected (declining an available shift) // If we are rejecting and can't find an application, create one as rejected (declining an available shift)
if (newStatus == dc.ApplicationStatus.REJECTED) { if (newStatus == dc.ApplicationStatus.REJECTED) {
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute(); final rolesResult = await _dataConnect
if (rolesResult.data.shiftRoles.isNotEmpty) { .listShiftRolesByShiftId(shiftId: shiftId)
final role = rolesResult.data.shiftRoles.first; .execute();
final staffId = await _getStaffId(); if (rolesResult.data.shiftRoles.isNotEmpty) {
await _dataConnect.createApplication( final role = rolesResult.data.shiftRoles.first;
final staffId = await _getStaffId();
await _dataConnect
.createApplication(
shiftId: shiftId, shiftId: shiftId,
staffId: staffId, staffId: staffId,
roleId: role.id, roleId: role.id,
status: dc.ApplicationStatus.REJECTED, status: dc.ApplicationStatus.REJECTED,
origin: dc.ApplicationOrigin.STAFF, origin: dc.ApplicationOrigin.STAFF,
).execute(); )
return; .execute();
} return;
}
} }
throw Exception("Application not found for shift $shiftId"); throw Exception("Application not found for shift $shiftId");
} }
await _dataConnect.updateApplicationStatus( await _dataConnect
id: appId, .updateApplicationStatus(id: appId, roleId: roleId)
roleId: roleId, .status(newStatus)
) .execute();
.status(newStatus)
.execute();
} }
} }

View File

@@ -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,
});
}

View File

@@ -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];
}

View File

@@ -6,7 +6,10 @@ import 'package:krow_domain/krow_domain.dart';
/// Implementations of this interface should reside in the data layer. /// Implementations of this interface should reside in the data layer.
abstract interface class ShiftsRepositoryInterface { abstract interface class ShiftsRepositoryInterface {
/// Retrieves the list of shifts assigned to the current user. /// 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]. /// Retrieves available shifts matching the given [query] and [type].
Future<List<Shift>> getAvailableShifts(String query, String type); Future<List<Shift>> getAvailableShifts(String query, String type);
@@ -15,12 +18,16 @@ abstract interface class ShiftsRepositoryInterface {
Future<List<Shift>> getPendingAssignments(); Future<List<Shift>> getPendingAssignments();
/// Retrieves detailed information for a specific shift by [shiftId]. /// 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. /// Applies for a specific open shift.
/// ///
/// [isInstantBook] determines if the application should be immediately accepted. /// [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. /// Accepts a pending shift assignment.
Future<void> acceptShift(String shiftId); Future<void> acceptShift(String shiftId);

View File

@@ -5,7 +5,15 @@ class ApplyForShiftUseCase {
ApplyForShiftUseCase(this.repository); ApplyForShiftUseCase(this.repository);
Future<void> call(String shiftId, {bool isInstantBook = false}) async { Future<void> call(
return repository.applyForShift(shiftId, isInstantBook: isInstantBook); String shiftId, {
bool isInstantBook = false,
String? roleId,
}) async {
return repository.applyForShift(
shiftId,
isInstantBook: isInstantBook,
roleId: roleId,
);
} }
} }

View File

@@ -1,18 +1,21 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../arguments/get_my_shifts_arguments.dart';
import '../repositories/shifts_repository_interface.dart'; import '../repositories/shifts_repository_interface.dart';
/// Use case for retrieving the user's assigned shifts. /// Use case for retrieving the user's assigned shifts.
/// ///
/// This use case delegates to [ShiftsRepositoryInterface]. /// This use case delegates to [ShiftsRepositoryInterface].
class GetMyShiftsUseCase extends NoInputUseCase<List<Shift>> { class GetMyShiftsUseCase extends UseCase<GetMyShiftsArguments, List<Shift>> {
final ShiftsRepositoryInterface repository; final ShiftsRepositoryInterface repository;
GetMyShiftsUseCase(this.repository); GetMyShiftsUseCase(this.repository);
@override @override
Future<List<Shift>> call() async { Future<List<Shift>> call(GetMyShiftsArguments arguments) async {
return repository.getMyShifts(); return repository.getMyShifts(
start: arguments.start,
end: arguments.end,
);
} }
} }

View File

@@ -1,14 +1,18 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../arguments/get_shift_details_arguments.dart';
import '../repositories/shifts_repository_interface.dart'; import '../repositories/shifts_repository_interface.dart';
class GetShiftDetailsUseCase extends UseCase<String, Shift?> { class GetShiftDetailsUseCase extends UseCase<GetShiftDetailsArguments, Shift?> {
final ShiftsRepositoryInterface repository; final ShiftsRepositoryInterface repository;
GetShiftDetailsUseCase(this.repository); GetShiftDetailsUseCase(this.repository);
@override @override
Future<Shift?> call(String params) { Future<Shift?> call(GetShiftDetailsArguments params) {
return repository.getShiftDetails(params); return repository.getShiftDetails(
params.shiftId,
roleId: params.roleId,
);
} }
} }

View File

@@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import '../../../domain/usecases/apply_for_shift_usecase.dart'; import '../../../domain/usecases/apply_for_shift_usecase.dart';
import '../../../domain/usecases/decline_shift_usecase.dart'; import '../../../domain/usecases/decline_shift_usecase.dart';
import '../../../domain/usecases/get_shift_details_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_event.dart';
import 'shift_details_state.dart'; import 'shift_details_state.dart';
@@ -26,7 +27,9 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
) async { ) async {
emit(ShiftDetailsLoading()); emit(ShiftDetailsLoading());
try { try {
final shift = await getShiftDetails(event.shiftId); final shift = await getShiftDetails(
GetShiftDetailsArguments(shiftId: event.shiftId, roleId: event.roleId),
);
if (shift != null) { if (shift != null) {
emit(ShiftDetailsLoaded(shift)); emit(ShiftDetailsLoaded(shift));
} else { } else {
@@ -42,8 +45,14 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
Emitter<ShiftDetailsState> emit, Emitter<ShiftDetailsState> emit,
) async { ) async {
try { try {
await applyForShift(event.shiftId, isInstantBook: true); await applyForShift(
emit(const ShiftActionSuccess("Shift successfully booked!")); event.shiftId,
isInstantBook: true,
roleId: event.roleId,
);
emit(
ShiftActionSuccess("Shift successfully booked!", shiftDate: event.date),
);
} catch (e) { } catch (e) {
emit(ShiftDetailsError(e.toString())); emit(ShiftDetailsError(e.toString()));
} }

View File

@@ -9,18 +9,21 @@ abstract class ShiftDetailsEvent extends Equatable {
class LoadShiftDetailsEvent extends ShiftDetailsEvent { class LoadShiftDetailsEvent extends ShiftDetailsEvent {
final String shiftId; final String shiftId;
const LoadShiftDetailsEvent(this.shiftId); final String? roleId;
const LoadShiftDetailsEvent(this.shiftId, {this.roleId});
@override @override
List<Object?> get props => [shiftId]; List<Object?> get props => [shiftId, roleId];
} }
class BookShiftDetailsEvent extends ShiftDetailsEvent { class BookShiftDetailsEvent extends ShiftDetailsEvent {
final String shiftId; final String shiftId;
const BookShiftDetailsEvent(this.shiftId); final String? roleId;
final DateTime? date;
const BookShiftDetailsEvent(this.shiftId, {this.roleId, this.date});
@override @override
List<Object?> get props => [shiftId]; List<Object?> get props => [shiftId, roleId, date];
} }
class DeclineShiftDetailsEvent extends ShiftDetailsEvent { class DeclineShiftDetailsEvent extends ShiftDetailsEvent {

View File

@@ -30,8 +30,9 @@ class ShiftDetailsError extends ShiftDetailsState {
class ShiftActionSuccess extends ShiftDetailsState { class ShiftActionSuccess extends ShiftDetailsState {
final String message; final String message;
const ShiftActionSuccess(this.message); final DateTime? shiftDate;
const ShiftActionSuccess(this.message, {this.shiftDate});
@override @override
List<Object?> get props => [message]; List<Object?> get props => [message, shiftDate];
} }

View File

@@ -4,6 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../../../domain/arguments/get_available_shifts_arguments.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_available_shifts_usecase.dart';
import '../../../domain/usecases/get_cancelled_shifts_usecase.dart'; import '../../../domain/usecases/get_cancelled_shifts_usecase.dart';
import '../../../domain/usecases/get_history_shifts_usecase.dart'; import '../../../domain/usecases/get_history_shifts_usecase.dart';
@@ -28,6 +29,8 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
required this.getHistoryShifts, required this.getHistoryShifts,
}) : super(ShiftsInitial()) { }) : super(ShiftsInitial()) {
on<LoadShiftsEvent>(_onLoadShifts); on<LoadShiftsEvent>(_onLoadShifts);
on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts); 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. // Or load all for simplicity as per prototype logic which had them all in memory.
try { 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 pendingResult = await getPendingAssignments();
final cancelledResult = await getCancelledShifts(); final cancelledResult = await getCancelledShifts();
final historyResult = await getHistoryShifts();
// Initial available with defaults // Initial available with defaults
final availableResult = await getAvailableShifts(const GetAvailableShiftsArguments()); final availableResult = await getAvailableShifts(const GetAvailableShiftsArguments());
@@ -56,7 +61,66 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
pendingShifts: pendingResult, pendingShifts: pendingResult,
cancelledShifts: cancelledResult, cancelledShifts: cancelledResult,
availableShifts: _filterPastShifts(availableResult), 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, 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: '', searchQuery: '',
jobType: 'all', 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) { List<Shift> _filterPastShifts(List<Shift> shifts) {
final now = DateTime.now(); final now = DateTime.now();
return shifts.where((shift) { return shifts.where((shift) {

View File

@@ -10,6 +10,21 @@ sealed class ShiftsEvent extends Equatable {
class LoadShiftsEvent extends ShiftsEvent {} 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 { class FilterAvailableShiftsEvent extends ShiftsEvent {
final String? query; final String? query;
final String? jobType; final String? jobType;

View File

@@ -18,6 +18,8 @@ class ShiftsLoaded extends ShiftsState {
final List<Shift> cancelledShifts; final List<Shift> cancelledShifts;
final List<Shift> availableShifts; final List<Shift> availableShifts;
final List<Shift> historyShifts; final List<Shift> historyShifts;
final bool historyLoading;
final bool historyLoaded;
final String searchQuery; final String searchQuery;
final String jobType; final String jobType;
@@ -27,6 +29,8 @@ class ShiftsLoaded extends ShiftsState {
required this.cancelledShifts, required this.cancelledShifts,
required this.availableShifts, required this.availableShifts,
required this.historyShifts, required this.historyShifts,
required this.historyLoading,
required this.historyLoaded,
required this.searchQuery, required this.searchQuery,
required this.jobType, required this.jobType,
}); });
@@ -37,6 +41,8 @@ class ShiftsLoaded extends ShiftsState {
List<Shift>? cancelledShifts, List<Shift>? cancelledShifts,
List<Shift>? availableShifts, List<Shift>? availableShifts,
List<Shift>? historyShifts, List<Shift>? historyShifts,
bool? historyLoading,
bool? historyLoaded,
String? searchQuery, String? searchQuery,
String? jobType, String? jobType,
}) { }) {
@@ -46,6 +52,8 @@ class ShiftsLoaded extends ShiftsState {
cancelledShifts: cancelledShifts ?? this.cancelledShifts, cancelledShifts: cancelledShifts ?? this.cancelledShifts,
availableShifts: availableShifts ?? this.availableShifts, availableShifts: availableShifts ?? this.availableShifts,
historyShifts: historyShifts ?? this.historyShifts, historyShifts: historyShifts ?? this.historyShifts,
historyLoading: historyLoading ?? this.historyLoading,
historyLoaded: historyLoaded ?? this.historyLoaded,
searchQuery: searchQuery ?? this.searchQuery, searchQuery: searchQuery ?? this.searchQuery,
jobType: jobType ?? this.jobType, jobType: jobType ?? this.jobType,
); );
@@ -58,6 +66,8 @@ class ShiftsLoaded extends ShiftsState {
cancelledShifts, cancelledShifts,
availableShifts, availableShifts,
historyShifts, historyShifts,
historyLoading,
historyLoaded,
searchQuery, searchQuery,
jobType, jobType,
]; ];

View File

@@ -2,7 +2,11 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
extension ShiftsNavigator on IModularNavigator { extension ShiftsNavigator on IModularNavigator {
void navigateToShiftsHome({DateTime? selectedDate}) {
navigate('/worker-main/shifts/', arguments: {'selectedDate': selectedDate});
}
void pushShiftDetails(Shift shift) { void pushShiftDetails(Shift shift) {
pushNamed('/worker-main/shift-details/${shift.id}', arguments: shift); navigate('/worker-main/shift-details/${shift.id}', arguments: shift);
} }
} }

View File

@@ -4,16 +4,25 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.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:design_system/design_system.dart'; // Re-added for UiIcons/Colors as they are used in expanded logic
import 'package:intl/intl.dart'; 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_bloc.dart';
import '../blocs/shift_details/shift_details_event.dart'; import '../blocs/shift_details/shift_details_event.dart';
import '../blocs/shift_details/shift_details_state.dart'; import '../blocs/shift_details/shift_details_state.dart';
class ShiftDetailsPage extends StatelessWidget { class ShiftDetailsPage extends StatefulWidget {
final String shiftId; final String shiftId;
final Shift? shift; final Shift? shift;
const ShiftDetailsPage({super.key, required this.shiftId, this.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) { String _formatTime(String time) {
if (time.isEmpty) return ''; if (time.isEmpty) return '';
try { try {
@@ -122,24 +131,37 @@ class ShiftDetailsPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<ShiftDetailsBloc>( return BlocProvider<ShiftDetailsBloc>(
create: (_) => create: (_) =>
Modular.get<ShiftDetailsBloc>()..add(LoadShiftDetailsEvent(shiftId)), Modular.get<ShiftDetailsBloc>()
..add(
LoadShiftDetailsEvent(
widget.shiftId,
roleId: widget.shift?.roleId,
),
),
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>( child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
listener: (context, state) { listener: (context, state) {
if (state is ShiftActionSuccess || state is ShiftDetailsError) {
_closeActionDialog(context);
}
if (state is ShiftActionSuccess) { if (state is ShiftActionSuccess) {
_isApplying = false;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(state.message), content: Text(state.message),
backgroundColor: const Color(0xFF10B981), backgroundColor: const Color(0xFF10B981),
), ),
); );
Modular.to.pop(true); // Return outcome Modular.to.navigateToShiftsHome(selectedDate: state.shiftDate);
} else if (state is ShiftDetailsError) { } else if (state is ShiftDetailsError) {
ScaffoldMessenger.of(context).showSnackBar( if (_isApplying || widget.shift == null) {
SnackBar( ScaffoldMessenger.of(context).showSnackBar(
content: Text(state.message), SnackBar(
backgroundColor: const Color(0xFFEF4444), content: Text(state.message),
), backgroundColor: const Color(0xFFEF4444),
); ),
);
}
_isApplying = false;
} }
}, },
child: BlocBuilder<ShiftDetailsBloc, ShiftDetailsState>( child: BlocBuilder<ShiftDetailsBloc, ShiftDetailsState>(
@@ -154,7 +176,7 @@ class ShiftDetailsPage extends StatelessWidget {
if (state is ShiftDetailsLoaded) { if (state is ShiftDetailsLoaded) {
displayShift = state.shift; displayShift = state.shift;
} else { } else {
displayShift = shift; displayShift = widget.shift;
} }
if (displayShift == null) { if (displayShift == null) {
@@ -164,7 +186,8 @@ class ShiftDetailsPage extends StatelessWidget {
} }
final duration = _calculateDuration(displayShift); final duration = _calculateDuration(displayShift);
final estimatedTotal = (displayShift.hourlyRate) * duration; final estimatedTotal =
displayShift.totalValue ?? (displayShift.hourlyRate * duration);
final openSlots = final openSlots =
(displayShift.requiredSlots ?? 0) - (displayShift.requiredSlots ?? 0) -
(displayShift.filledSlots ?? 0); (displayShift.filledSlots ?? 0);
@@ -172,8 +195,8 @@ class ShiftDetailsPage extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: UiAppBar( appBar: UiAppBar(
title: displayShift.title, title: displayShift.title,
showBackButton: true,
centerTitle: false, centerTitle: false,
onLeadingPressed: () => Modular.to.navigateToShiftsHome(),
), ),
body: Column( body: Column(
children: [ children: [
@@ -396,14 +419,14 @@ class ShiftDetailsPage extends StatelessWidget {
color: UiColors.textPrimary, color: UiColors.textPrimary,
), ),
), ),
Text( Text(
displayShift.location.isEmpty displayShift.location.isEmpty
? "TBD" ? "TBD"
: displayShift.locationAddress, : displayShift.locationAddress,
style: UiTypography.title1m.copyWith( style: UiTypography.title1m.copyWith(
color: UiColors.textPrimary, color: UiColors.textPrimary,
), ),
), ),
], ],
), ),
], ],
@@ -438,41 +461,53 @@ class ShiftDetailsPage extends StatelessWidget {
), ),
], ],
const SizedBox(height: 20), const SizedBox(height: 20),
Row( if (displayShift!.status != 'confirmed' &&
children: [ displayShift!.hasApplied != true &&
Expanded( (displayShift!.requiredSlots == null ||
child: OutlinedButton( displayShift!.filledSlots == null ||
onPressed: () => displayShift!.filledSlots! <
_declineShift(context, displayShift!.id), displayShift!.requiredSlots!))
style: OutlinedButton.styleFrom( Row(
foregroundColor: const Color(0xFFEF4444), children: [
side: const BorderSide( Expanded(
color: Color(0xFFEF4444), child: OutlinedButton(
onPressed: () => _declineShift(
context,
displayShift!.id,
), ),
padding: const EdgeInsets.symmetric( style: OutlinedButton.styleFrom(
vertical: 16, 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),
const SizedBox(width: 16), Expanded(
Expanded( child: ElevatedButton(
child: ElevatedButton( onPressed: () => _bookShift(
onPressed: () => context,
_bookShift(context, displayShift!.id), displayShift!,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF10B981),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
), ),
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( SizedBox(
height: MediaQuery.of(context).padding.bottom + 10, 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( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
@@ -497,15 +535,20 @@ class ShiftDetailsPage extends StatelessWidget {
content: const Text('Do you want to instantly book this shift?'), content: const Text('Do you want to instantly book this shift?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(ctx).pop(), onPressed: () => Modular.to.pop(),
child: const Text('Cancel'), child: const Text('Cancel'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(ctx).pop(); Modular.to.pop();
BlocProvider.of<ShiftDetailsBloc>( _showApplyingDialog(context, shift);
context, BlocProvider.of<ShiftDetailsBloc>(context).add(
).add(BookShiftDetailsEvent(id)); BookShiftDetailsEvent(
shift.id,
roleId: shift.roleId,
date: DateTime.tryParse(shift.date),
),
);
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: const Color(0xFF10B981), foregroundColor: const Color(0xFF10B981),
@@ -527,12 +570,11 @@ class ShiftDetailsPage extends StatelessWidget {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(ctx).pop(), onPressed: () => Modular.to.pop(),
child: const Text('Cancel'), child: const Text('Cancel'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(ctx).pop();
BlocProvider.of<ShiftDetailsBloc>( BlocProvider.of<ShiftDetailsBloc>(
context, context,
).add(DeclineShiftDetailsEvent(id)); ).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;
}
} }

View File

@@ -11,7 +11,8 @@ import '../styles/shifts_styles.dart';
class ShiftsPage extends StatefulWidget { class ShiftsPage extends StatefulWidget {
final String? initialTab; final String? initialTab;
const ShiftsPage({super.key, this.initialTab}); final DateTime? selectedDate;
const ShiftsPage({super.key, this.initialTab, this.selectedDate});
@override @override
State<ShiftsPage> createState() => _ShiftsPageState(); State<ShiftsPage> createState() => _ShiftsPageState();
@@ -19,13 +20,18 @@ class ShiftsPage extends StatefulWidget {
class _ShiftsPageState extends State<ShiftsPage> { class _ShiftsPageState extends State<ShiftsPage> {
late String _activeTab; late String _activeTab;
DateTime? _selectedDate;
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>(); final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_activeTab = widget.initialTab ?? 'myshifts'; _activeTab = widget.initialTab ?? 'myshifts';
_selectedDate = widget.selectedDate;
_bloc.add(LoadShiftsEvent()); _bloc.add(LoadShiftsEvent());
if (_activeTab == 'history') {
_bloc.add(LoadHistoryShiftsEvent());
}
} }
@override @override
@@ -36,6 +42,11 @@ class _ShiftsPageState extends State<ShiftsPage> {
_activeTab = widget.initialTab!; _activeTab = widget.initialTab!;
}); });
} }
if (widget.selectedDate != null && widget.selectedDate != _selectedDate) {
setState(() {
_selectedDate = widget.selectedDate;
});
}
} }
@override @override
@@ -59,6 +70,12 @@ class _ShiftsPageState extends State<ShiftsPage> {
final List<Shift> historyShifts = (state is ShiftsLoaded) final List<Shift> historyShifts = (state is ShiftsLoaded)
? state.historyShifts ? 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: "filteredJobs" logic moved to FindShiftsTab
// Note: Calendar logic moved to MyShiftsTab // Note: Calendar logic moved to MyShiftsTab
@@ -103,7 +120,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
"find", "find",
"Find Shifts", "Find Shifts",
UiIcons.search, 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), const SizedBox(width: 8),
_buildTab( _buildTab(
@@ -111,6 +129,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
"History", "History",
UiIcons.clock, UiIcons.clock,
historyShifts.length, historyShifts.length,
showCount: historyLoaded,
), ),
], ],
), ),
@@ -124,11 +143,12 @@ class _ShiftsPageState extends State<ShiftsPage> {
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: _buildTabContent( : _buildTabContent(
myShifts, myShifts,
pendingAssignments, pendingAssignments,
cancelledShifts, cancelledShifts,
availableJobs, availableJobs,
historyShifts, historyShifts,
), historyLoading,
),
), ),
], ],
), ),
@@ -144,6 +164,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
List<Shift> cancelledShifts, List<Shift> cancelledShifts,
List<Shift> availableJobs, List<Shift> availableJobs,
List<Shift> historyShifts, List<Shift> historyShifts,
bool historyLoading,
) { ) {
switch (_activeTab) { switch (_activeTab) {
case 'myshifts': case 'myshifts':
@@ -151,25 +172,36 @@ class _ShiftsPageState extends State<ShiftsPage> {
myShifts: myShifts, myShifts: myShifts,
pendingAssignments: pendingAssignments, pendingAssignments: pendingAssignments,
cancelledShifts: cancelledShifts, cancelledShifts: cancelledShifts,
initialDate: _selectedDate,
); );
case 'find': case 'find':
return FindShiftsTab( return FindShiftsTab(availableJobs: availableJobs);
availableJobs: availableJobs,
);
case 'history': case 'history':
return HistoryShiftsTab( if (historyLoading) {
historyShifts: historyShifts, return const Center(child: CircularProgressIndicator());
); }
return HistoryShiftsTab(historyShifts: historyShifts);
default: default:
return const SizedBox.shrink(); 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; final isActive = _activeTab == id;
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => setState(() => _activeTab = id), onTap: () {
setState(() => _activeTab = id);
if (id == 'history') {
_bloc.add(LoadHistoryShiftsEvent());
}
},
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -199,27 +231,32 @@ class _ShiftsPageState extends State<ShiftsPage> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 4), if (showCount) ...[
Container( const SizedBox(width: 4),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), Container(
constraints: const BoxConstraints(minWidth: 18), padding: const EdgeInsets.symmetric(
decoration: BoxDecoration( horizontal: 6,
color: isActive vertical: 2,
? AppColors.krowBlue.withAlpha((0.1 * 255).round()) ),
: Colors.white.withAlpha((0.2 * 255).round()), constraints: const BoxConstraints(minWidth: 18),
borderRadius: BorderRadius.circular(999), decoration: BoxDecoration(
), color: isActive
child: Center( ? AppColors.krowBlue.withAlpha((0.1 * 255).round())
child: Text( : Colors.white.withAlpha((0.2 * 255).round()),
"$count", borderRadius: BorderRadius.circular(999),
style: TextStyle( ),
fontSize: 10, child: Center(
fontWeight: FontWeight.bold, child: Text(
color: isActive ? AppColors.krowBlue : Colors.white, "$count",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: isActive ? AppColors.krowBlue : Colors.white,
),
), ),
), ),
), ),
), ],
], ],
), ),
), ),

View File

@@ -175,7 +175,7 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
), ),
), ),
), ),
const SizedBox(height: 40), const SizedBox(height: UiConstants.space32),
], ],
), ),
), ),

View File

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