diff --git a/.github/workflows/mobile-ci.yml b/.github/workflows/mobile-ci.yml index 5415ae44..1a439740 100644 --- a/.github/workflows/mobile-ci.yml +++ b/.github/workflows/mobile-ci.yml @@ -29,10 +29,10 @@ jobs: id: detect run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then - # For PR, compare with base branch + # For PR, compare all changes against base branch (not just latest commit) + # Using three-dot syntax (...) shows all files changed in the PR branch BASE_REF="${{ github.event.pull_request.base.ref }}" - HEAD_REF="${{ github.event.pull_request.head.ref }}" - CHANGED_FILES=$(git diff --name-only origin/$BASE_REF..origin/$HEAD_REF 2>/dev/null || echo "") + CHANGED_FILES=$(git diff --name-only origin/$BASE_REF...HEAD 2>/dev/null || echo "") else # For push, compare with previous commit if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then @@ -74,39 +74,47 @@ jobs: - name: ๐Ÿฆ‹ Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.19.x' + flutter-version: '3.38.x' channel: 'stable' cache: true + - name: ๐Ÿ”ง Install Firebase CLI + run: | + npm install -g firebase-tools + - name: ๐Ÿ“ฆ Get Flutter dependencies run: | - cd apps/mobile - flutter pub get + make mobile-install - name: ๐Ÿ”จ Run compilation check run: | - cd apps/mobile - echo "โš™๏ธ Running build_runner..." - flutter pub run build_runner build --delete-conflicting-outputs 2>&1 || true - - echo "" - echo "๐Ÿ”ฌ Running flutter analyze on all files..." - flutter analyze lib/ --no-fatal-infos 2>&1 | tee analyze_output.txt || true + set -o pipefail + + echo "๐Ÿ—๏ธ Building client app for Android (dev mode)..." + if ! make mobile-client-build PLATFORM=apk MODE=debug 2>&1 | tee client_build.txt; then + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "โŒ CLIENT APP BUILD FAILED" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + exit 1 + fi echo "" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - # Check for actual errors (not just warnings) - if grep -E "^\s*(error|SEVERE):" analyze_output.txt > /dev/null; then - echo "โŒ COMPILATION ERRORS FOUND:" + echo "๐Ÿ—๏ธ Building staff app for Android (dev mode)..." + if ! make mobile-staff-build PLATFORM=apk MODE=debug 2>&1 | tee staff_build.txt; then + echo "" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - grep -B 2 -A 1 -E "^\s*(error|SEVERE):" analyze_output.txt | sed 's/^/ /' + echo "โŒ STAFF APP BUILD FAILED" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" exit 1 - else - echo "โœ… Compilation check PASSED - No errors found" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" fi + + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "โœ… Build check PASSED - Both apps built successfully" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" lint: name: ๐Ÿงน Lint Changed Files @@ -120,18 +128,21 @@ jobs: - name: ๐Ÿฆ‹ Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.19.x' + flutter-version: '3.38.x' channel: 'stable' cache: true + - name: ๐Ÿ”ง Install Firebase CLI + run: | + npm install -g firebase-tools + - name: ๐Ÿ“ฆ Get Flutter dependencies run: | - cd apps/mobile - flutter pub get + make mobile-install - name: ๐Ÿ” Lint changed Dart files run: | - cd apps/mobile + set -o pipefail # Get the list of changed files CHANGED_FILES="${{ needs.detect-changes.outputs.changed-files }}" @@ -153,7 +164,7 @@ jobs: if [[ -n "$file" && "$file" == *.dart && -f "$file" ]]; then echo "๐Ÿ“ Analyzing: $file" - if ! flutter analyze "$file" --no-fatal-infos 2>&1 | tee -a lint_output.txt; then + if ! dart analyze "$file" 2>&1 | tee -a lint_output.txt; then HAS_ERRORS=true FAILED_FILES+=("$file") fi diff --git a/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart b/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart index c2cf156f..5da83e16 100644 --- a/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart +++ b/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart @@ -98,7 +98,11 @@ extension StaffNavigator on IModularNavigator { /// Parameters: /// * [selectedDate] - Optional date to pre-select in the shifts view /// * [initialTab] - Optional initial tab (via query parameter) - void toShifts({DateTime? selectedDate, String? initialTab}) { + void toShifts({ + DateTime? selectedDate, + String? initialTab, + bool? refreshAvailable, + }) { final Map args = {}; if (selectedDate != null) { args['selectedDate'] = selectedDate; @@ -106,6 +110,9 @@ extension StaffNavigator on IModularNavigator { if (initialTab != null) { args['initialTab'] = initialTab; } + if (refreshAvailable == true) { + args['refreshAvailable'] = true; + } navigate( StaffPaths.shifts, arguments: args.isEmpty ? null : args, diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart index 752bd2d4..ccfd5bfd 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart @@ -2,6 +2,38 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/shifts/break/break.dart'; class Shift extends Equatable { + final String id; + final String title; + final String clientName; + final String? logoUrl; + final double hourlyRate; + final String location; + final String locationAddress; + final String date; + final String startTime; + final String endTime; + final String createdDate; + final bool? tipsAvailable; + final bool? travelTime; + final bool? mealProvided; + final bool? parkingAvailable; + final bool? gasCompensation; + final String? description; + final String? instructions; + final List? managers; + final double? latitude; + final double? longitude; + final String? status; + final int? durationDays; // For multi-day shifts + final int? requiredSlots; + final int? filledSlots; + final String? roleId; + final bool? hasApplied; + final double? totalValue; + final Break? breakInfo; + final String? orderId; + final String? orderType; + final List? schedules; const Shift({ required this.id, @@ -33,6 +65,9 @@ class Shift extends Equatable { this.hasApplied, this.totalValue, this.breakInfo, + this.orderId, + this.orderType, + this.schedules, }); final String id; final String title; @@ -95,9 +130,27 @@ class Shift extends Equatable { hasApplied, totalValue, breakInfo, + orderId, + orderType, + schedules, ]; } +class ShiftSchedule extends Equatable { + const ShiftSchedule({ + required this.date, + required this.startTime, + required this.endTime, + }); + + final String date; + final String startTime; + final String endTime; + + @override + List get props => [date, startTime, endTime]; +} + class ShiftManager extends Equatable { const ShiftManager({required this.name, required this.phone, this.avatar}); diff --git a/apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart b/apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart index cd6c15fb..221a07a3 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart @@ -105,7 +105,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte .state(hub.state) .street(hub.street) .country(hub.country) - .status(dc.ShiftStatus.CONFIRMED) + .status(dc.ShiftStatus.OPEN) .workersNeeded(workersNeeded) .filled(0) .durationDays(1) @@ -224,7 +224,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte .state(hub.state) .street(hub.street) .country(hub.country) - .status(dc.ShiftStatus.CONFIRMED) + .status(dc.ShiftStatus.OPEN) .workersNeeded(workersNeeded) .filled(0) .durationDays(1) @@ -342,7 +342,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte .state(hub.state) .street(hub.street) .country(hub.country) - .status(dc.ShiftStatus.CONFIRMED) + .status(dc.ShiftStatus.OPEN) .workersNeeded(workersNeeded) .filled(0) .durationDays(1) diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/permanent_order/permanent_order_view.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/permanent_order/permanent_order_view.dart index 888bd150..c5041687 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/permanent_order/permanent_order_view.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/permanent_order/permanent_order_view.dart @@ -20,6 +20,42 @@ class PermanentOrderView extends StatelessWidget { /// Creates a [PermanentOrderView]. const PermanentOrderView({super.key}); + DateTime _firstPermanentShiftDate( + DateTime startDate, + List permanentDays, + ) { + final DateTime start = DateTime(startDate.year, startDate.month, startDate.day); + final DateTime end = start.add(const Duration(days: 29)); + final Set selected = permanentDays.toSet(); + for (DateTime day = start; !day.isAfter(end); day = day.add(const Duration(days: 1))) { + if (selected.contains(_weekdayLabel(day))) { + return day; + } + } + return start; + } + + String _weekdayLabel(DateTime date) { + switch (date.weekday) { + case DateTime.monday: + return 'MON'; + case DateTime.tuesday: + return 'TUE'; + case DateTime.wednesday: + return 'WED'; + case DateTime.thursday: + return 'THU'; + case DateTime.friday: + return 'FRI'; + case DateTime.saturday: + return 'SAT'; + case DateTime.sunday: + return 'SUN'; + default: + return 'SUN'; + } + } + @override Widget build(BuildContext context) { final TranslationsClientCreateOrderPermanentEn labels = @@ -42,6 +78,10 @@ class PermanentOrderView extends StatelessWidget { }, builder: (BuildContext context, PermanentOrderState state) { if (state.status == PermanentOrderStatus.success) { + final DateTime initialDate = _firstPermanentShiftDate( + state.startDate, + state.permanentDays, + ); return PermanentOrderSuccessView( title: labels.title, message: labels.subtitle, @@ -50,7 +90,7 @@ class PermanentOrderView extends StatelessWidget { ClientPaths.orders, (_) => false, arguments: { - 'initialDate': state.startDate.toIso8601String(), + 'initialDate': initialDate.toIso8601String(), }, ), ); diff --git a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/recurring_order/recurring_order_view.dart b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/recurring_order/recurring_order_view.dart index 89a20519..a6f173c8 100644 --- a/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/recurring_order/recurring_order_view.dart +++ b/apps/mobile/packages/features/client/create_order/lib/src/presentation/widgets/recurring_order/recurring_order_view.dart @@ -20,6 +20,43 @@ class RecurringOrderView extends StatelessWidget { /// Creates a [RecurringOrderView]. const RecurringOrderView({super.key}); + DateTime _firstRecurringShiftDate( + DateTime startDate, + DateTime endDate, + List recurringDays, + ) { + final DateTime start = DateTime(startDate.year, startDate.month, startDate.day); + final DateTime end = DateTime(endDate.year, endDate.month, endDate.day); + final Set selected = recurringDays.toSet(); + for (DateTime day = start; !day.isAfter(end); day = day.add(const Duration(days: 1))) { + if (selected.contains(_weekdayLabel(day))) { + return day; + } + } + return start; + } + + String _weekdayLabel(DateTime date) { + switch (date.weekday) { + case DateTime.monday: + return 'MON'; + case DateTime.tuesday: + return 'TUE'; + case DateTime.wednesday: + return 'WED'; + case DateTime.thursday: + return 'THU'; + case DateTime.friday: + return 'FRI'; + case DateTime.saturday: + return 'SAT'; + case DateTime.sunday: + return 'SUN'; + default: + return 'SUN'; + } + } + @override Widget build(BuildContext context) { final TranslationsClientCreateOrderRecurringEn labels = @@ -44,6 +81,15 @@ class RecurringOrderView extends StatelessWidget { }, builder: (BuildContext context, RecurringOrderState state) { if (state.status == RecurringOrderStatus.success) { + final DateTime maxEndDate = + state.startDate.add(const Duration(days: 29)); + final DateTime effectiveEndDate = + state.endDate.isAfter(maxEndDate) ? maxEndDate : state.endDate; + final DateTime initialDate = _firstRecurringShiftDate( + state.startDate, + effectiveEndDate, + state.recurringDays, + ); return RecurringOrderSuccessView( title: labels.title, message: labels.subtitle, @@ -52,7 +98,7 @@ class RecurringOrderView extends StatelessWidget { ClientPaths.orders, (_) => false, arguments: { - 'initialDate': state.startDate.toIso8601String(), + 'initialDate': initialDate.toIso8601String(), }, ), ); diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart index fa0e4a71..8bb83203 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart @@ -265,7 +265,7 @@ class _ShiftOrderFormSheetState extends State { .state(selectedHub.state) .street(selectedHub.street) .country(selectedHub.country) - .status(dc.ShiftStatus.PENDING) + .status(dc.ShiftStatus.OPEN) .workersNeeded(workersNeeded) .filled(0) .durationDays(1) diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart index a41c5e1f..07b2d4d5 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart @@ -46,6 +46,145 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface { Future> getHistoryShifts() async { final staffId = await _service.getStaffId(); return _connectorRepository.getHistoryShifts(staffId: staffId); + final fdc.QueryResult response = await _service.executeProtected(() => _service.connector + .listCompletedApplicationsByStaffId(staffId: staffId) + .execute()); + final List shifts = []; + + for (final app in response.data.applications) { + _shiftToAppIdMap[app.shift.id] = app.id; + _appToRoleIdMap[app.id] = app.shiftRole.id; + + final String roleName = app.shiftRole.role.name; + final String orderName = + (app.shift.order.eventName ?? '').trim().isNotEmpty + ? app.shift.order.eventName! + : app.shift.order.business.businessName; + final String title = '$roleName - $orderName'; + final DateTime? shiftDate = _service.toDateTime(app.shift.date); + final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime); + final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime); + final DateTime? createdDt = _service.toDateTime(app.createdAt); + + shifts.add( + Shift( + id: app.shift.id, + roleId: app.shiftRole.roleId, + title: title, + clientName: app.shift.order.business.businessName, + logoUrl: app.shift.order.business.companyLogoUrl, + hourlyRate: app.shiftRole.role.costPerHour, + location: app.shift.location ?? '', + locationAddress: app.shift.order.teamHub.hubName, + date: shiftDate?.toIso8601String() ?? '', + startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '', + endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', + createdDate: createdDt?.toIso8601String() ?? '', + status: _mapStatus(dc.ApplicationStatus.CHECKED_OUT), + description: app.shift.description, + durationDays: app.shift.durationDays, + requiredSlots: app.shiftRole.count, + filledSlots: app.shiftRole.assigned ?? 0, + hasApplied: true, + latitude: app.shift.latitude, + longitude: app.shift.longitude, + breakInfo: BreakAdapter.fromData( + isPaid: app.shiftRole.isBreakPaid ?? false, + breakTime: app.shiftRole.breakType?.stringValue, + ), + ), + ); + } + return shifts; + } + + Future> _fetchApplications({ + DateTime? start, + DateTime? end, + }) async { + final staffId = await _service.getStaffId(); + var query = _service.connector.getMyApplicationsByStaffId(staffId: staffId); + if (start != null && end != null) { + query = query.dayStart(_service.toTimestamp(start)).dayEnd(_service.toTimestamp(end)); + } + final fdc.QueryResult response = + await _service.executeProtected(() => query.execute()); + + final apps = response.data.applications; + final List shifts = []; + + for (final app in apps) { + _shiftToAppIdMap[app.shift.id] = app.id; + _appToRoleIdMap[app.id] = app.shiftRole.id; + + final String roleName = app.shiftRole.role.name; + final String orderName = + (app.shift.order.eventName ?? '').trim().isNotEmpty + ? app.shift.order.eventName! + : app.shift.order.business.businessName; + final String title = '$roleName - $orderName'; + final DateTime? shiftDate = _service.toDateTime(app.shift.date); + final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime); + final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime); + final DateTime? createdDt = _service.toDateTime(app.createdAt); + + // Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED) + final bool hasCheckIn = app.checkInTime != null; + final bool hasCheckOut = app.checkOutTime != null; + dc.ApplicationStatus? appStatus; + if (app.status is dc.Known) { + appStatus = (app.status as dc.Known).value; + } + final String mappedStatus = hasCheckOut + ? 'completed' + : hasCheckIn + ? 'checked_in' + : _mapStatus(appStatus ?? dc.ApplicationStatus.CONFIRMED); + shifts.add( + Shift( + id: app.shift.id, + roleId: app.shiftRole.roleId, + title: title, + clientName: app.shift.order.business.businessName, + logoUrl: app.shift.order.business.companyLogoUrl, + hourlyRate: app.shiftRole.role.costPerHour, + location: app.shift.location ?? '', + locationAddress: app.shift.order.teamHub.hubName, + date: shiftDate?.toIso8601String() ?? '', + startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '', + endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', + createdDate: createdDt?.toIso8601String() ?? '', + status: mappedStatus, + description: app.shift.description, + durationDays: app.shift.durationDays, + requiredSlots: app.shiftRole.count, + filledSlots: app.shiftRole.assigned ?? 0, + hasApplied: true, + latitude: app.shift.latitude, + longitude: app.shift.longitude, + breakInfo: BreakAdapter.fromData( + isPaid: app.shiftRole.isBreakPaid ?? false, + breakTime: app.shiftRole.breakType?.stringValue, + ), + ), + ); + } + return shifts; + } + + String _mapStatus(dc.ApplicationStatus status) { + switch (status) { + case dc.ApplicationStatus.CONFIRMED: + return 'confirmed'; + case dc.ApplicationStatus.PENDING: + return 'pending'; + case dc.ApplicationStatus.CHECKED_OUT: + return 'completed'; + case dc.ApplicationStatus.REJECTED: + return 'cancelled'; + default: + return 'open'; + } } @override @@ -56,6 +195,76 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface { query: query, type: type, ); + final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId; + if (vendorId == null || vendorId.isEmpty) { + return []; + } + + final fdc.QueryResult result = await _service.executeProtected(() => _service.connector + .listShiftRolesByVendorId(vendorId: vendorId) + .execute()); + + final allShiftRoles = result.data.shiftRoles; + + // Fetch my applications to filter out already booked shifts + final List myShifts = await _fetchApplications(); + final Set myShiftIds = myShifts.map((s) => s.id).toSet(); + + final List mappedShifts = []; + for (final sr in allShiftRoles) { + // Skip if I have already applied/booked this shift + if (myShiftIds.contains(sr.shiftId)) continue; + + + final DateTime? shiftDate = _service.toDateTime(sr.shift.date); + final startDt = _service.toDateTime(sr.startTime); + final endDt = _service.toDateTime(sr.endTime); + final createdDt = _service.toDateTime(sr.createdAt); + + mappedShifts.add( + Shift( + id: sr.shiftId, + roleId: sr.roleId, + title: sr.role.name, + clientName: sr.shift.order.business.businessName, + logoUrl: null, + hourlyRate: sr.role.costPerHour, + location: sr.shift.location ?? '', + locationAddress: sr.shift.locationAddress ?? '', + date: shiftDate?.toIso8601String() ?? '', + startTime: startDt != null + ? DateFormat('HH:mm').format(startDt) + : '', + endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', + createdDate: createdDt?.toIso8601String() ?? '', + status: sr.shift.status?.stringValue.toLowerCase() ?? 'open', + description: sr.shift.description, + durationDays: sr.shift.durationDays, + requiredSlots: sr.count, + filledSlots: sr.assigned ?? 0, + latitude: sr.shift.latitude, + longitude: sr.shift.longitude, + orderId: sr.shift.order.id, + orderType: sr.shift.order.orderType?.stringValue, + breakInfo: BreakAdapter.fromData( + isPaid: sr.isBreakPaid ?? false, + breakTime: sr.breakType?.stringValue, + ), + ), + ); + } + + if (query.isNotEmpty) { + return mappedShifts + .where( + (s) => + s.title.toLowerCase().contains(query.toLowerCase()) || + s.clientName.toLowerCase().contains(query.toLowerCase()), + ) + .toList(); + } + + return mappedShifts; } @override diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart index 568b7349..2b42b3de 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart @@ -112,9 +112,15 @@ class ShiftsBloc extends Bloc ) async { final currentState = state; if (currentState is! ShiftsLoaded) return; - if (currentState.availableLoading || currentState.availableLoaded) return; + if (!event.force && + (currentState.availableLoading || currentState.availableLoaded)) { + return; + } - emit(currentState.copyWith(availableLoading: true)); + emit(currentState.copyWith( + availableLoading: true, + availableLoaded: false, + )); await handleError( emit: emit.call, action: () async { diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart index e076c6bc..7e1632d2 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart @@ -12,7 +12,13 @@ class LoadShiftsEvent extends ShiftsEvent {} class LoadHistoryShiftsEvent extends ShiftsEvent {} -class LoadAvailableShiftsEvent extends ShiftsEvent {} +class LoadAvailableShiftsEvent extends ShiftsEvent { + final bool force; + const LoadAvailableShiftsEvent({this.force = false}); + + @override + List get props => [force]; +} class LoadFindFirstEvent extends ShiftsEvent {} diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart index e4563de1..b2a17a60 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shift_details_page.dart @@ -93,7 +93,11 @@ class _ShiftDetailsPageState extends State { message: state.message, type: UiSnackbarType.success, ); - Modular.to.toShifts(selectedDate: state.shiftDate); + Modular.to.toShifts( + selectedDate: state.shiftDate, + initialTab: 'find', + refreshAvailable: true, + ); } else if (state is ShiftDetailsError) { if (_isApplying) { UiSnackbar.show( @@ -112,7 +116,8 @@ class _ShiftDetailsPageState extends State { ); } - Shift displayShift = widget.shift; + final Shift displayShift = + state is ShiftDetailsLoaded ? state.shift : widget.shift; final i18n = Translations.of(context).staff_shifts.shift_details; final duration = _calculateDuration(displayShift); diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart index 6d707901..ceda2b68 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart @@ -12,7 +12,13 @@ import '../widgets/tabs/history_shifts_tab.dart'; class ShiftsPage extends StatefulWidget { final String? initialTab; final DateTime? selectedDate; - const ShiftsPage({super.key, this.initialTab, this.selectedDate}); + final bool refreshAvailable; + const ShiftsPage({ + super.key, + this.initialTab, + this.selectedDate, + this.refreshAvailable = false, + }); @override State createState() => _ShiftsPageState(); @@ -22,6 +28,8 @@ class _ShiftsPageState extends State { late String _activeTab; DateTime? _selectedDate; bool _prioritizeFind = false; + bool _refreshAvailable = false; + bool _pendingAvailableRefresh = false; final ShiftsBloc _bloc = Modular.get(); @override @@ -30,6 +38,8 @@ class _ShiftsPageState extends State { _activeTab = widget.initialTab ?? 'myshifts'; _selectedDate = widget.selectedDate; _prioritizeFind = widget.initialTab == 'find'; + _refreshAvailable = widget.refreshAvailable; + _pendingAvailableRefresh = widget.refreshAvailable; if (_prioritizeFind) { _bloc.add(LoadFindFirstEvent()); } else { @@ -40,7 +50,9 @@ class _ShiftsPageState extends State { } if (_activeTab == 'find') { if (!_prioritizeFind) { - _bloc.add(LoadAvailableShiftsEvent()); + _bloc.add( + LoadAvailableShiftsEvent(force: _refreshAvailable), + ); } } // Check profile completion @@ -61,6 +73,10 @@ class _ShiftsPageState extends State { _selectedDate = widget.selectedDate; }); } + if (widget.refreshAvailable) { + _refreshAvailable = true; + _pendingAvailableRefresh = true; + } } @override @@ -79,6 +95,10 @@ class _ShiftsPageState extends State { } }, builder: (context, state) { + if (_pendingAvailableRefresh && state is ShiftsLoaded) { + _pendingAvailableRefresh = false; + _bloc.add(const LoadAvailableShiftsEvent(force: true)); + } final bool baseLoaded = state is ShiftsLoaded; final List myShifts = (state is ShiftsLoaded) ? state.myShifts diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart index 86352524..03f20b49 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart @@ -77,6 +77,13 @@ class _MyShiftCardState extends State { String _getShiftType() { // Handling potential localization key availability try { + final String orderType = (widget.shift.orderType ?? '').toUpperCase(); + if (orderType == 'PERMANENT') { + return t.staff_shifts.filter.long_term; + } + if (orderType == 'RECURRING') { + return t.staff_shifts.filter.multi_day; + } if (widget.shift.durationDays != null && widget.shift.durationDays! > 30) { return t.staff_shifts.filter.long_term; } @@ -133,6 +140,24 @@ class _MyShiftCardState extends State { statusText = status?.toUpperCase() ?? ""; } + final schedules = widget.shift.schedules ?? []; + final hasSchedules = schedules.isNotEmpty; + final List visibleSchedules = schedules.length <= 5 + ? schedules + : schedules.take(3).toList(); + final int remainingSchedules = + schedules.length <= 5 ? 0 : schedules.length - 3; + final String scheduleRange = hasSchedules + ? () { + final first = schedules.first.date; + final last = schedules.last.date; + if (first == last) { + return _formatDate(first); + } + return '${_formatDate(first)} โ€“ ${_formatDate(last)}'; + }() + : ''; + return GestureDetector( onTap: () { Modular.to.pushNamed( @@ -191,8 +216,8 @@ class _MyShiftCardState extends State { letterSpacing: 0.5, ), ), - // Shift Type Badge - if (status == 'open' || status == 'pending') ...[ + // Shift Type Badge (Order type) + if ((widget.shift.orderType ?? '').isNotEmpty) ...[ const SizedBox(width: UiConstants.space2), Container( padding: const EdgeInsets.symmetric( @@ -200,13 +225,14 @@ class _MyShiftCardState extends State { vertical: 2, ), decoration: BoxDecoration( - color: UiColors.primary.withValues(alpha: 0.1), + color: UiColors.background, borderRadius: UiConstants.radiusSm, + border: Border.all(color: UiColors.border), ), child: Text( _getShiftType(), style: UiTypography.footnote2m.copyWith( - color: UiColors.primary, + color: UiColors.textSecondary, ), ), ), @@ -299,7 +325,55 @@ class _MyShiftCardState extends State { const SizedBox(height: UiConstants.space2), // Date & Time - if (widget.shift.durationDays != null && + if (hasSchedules) ...[ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + UiIcons.clock, + size: UiConstants.iconXs, + color: UiColors.primary, + ), + const SizedBox(width: UiConstants.space1), + Text( + '${schedules.length} schedules', + style: UiTypography.footnote2m.copyWith( + color: UiColors.primary, + ), + ), + ], + ), + const SizedBox(height: UiConstants.space1), + Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + scheduleRange, + style: UiTypography.footnote2r.copyWith(color: UiColors.primary), + ), + ), + ...visibleSchedules.map( + (schedule) => Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + '${_formatDate(schedule.date)}, ${_formatTime(schedule.startTime)} โ€“ ${_formatTime(schedule.endTime)}', + style: UiTypography.footnote2r.copyWith( + color: UiColors.primary, + ), + ), + ), + ), + if (remainingSchedules > 0) + Text( + '+$remainingSchedules more schedules', + style: UiTypography.footnote2r.copyWith( + color: UiColors.primary.withOpacity(0.7), + ), + ), + ], + ), + ] else if (widget.shift.durationDays != null && widget.shift.durationDays! > 1) ...[ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -324,17 +398,22 @@ class _MyShiftCardState extends State { ), const SizedBox(height: UiConstants.space1), Padding( - padding: const EdgeInsets.only(bottom: 2), - child: Text( - '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} โ€“ ${_formatTime(widget.shift.endTime)}', - style: UiTypography.footnote2r.copyWith(color: UiColors.primary), + padding: const EdgeInsets.only(bottom: 2), + child: Text( + '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} โ€“ ${_formatTime(widget.shift.endTime)}', + style: UiTypography.footnote2r.copyWith( + color: UiColors.primary, ), + ), ), if (widget.shift.durationDays! > 1) - Text( - '... +${widget.shift.durationDays! - 1} more days', - style: UiTypography.footnote2r.copyWith(color: UiColors.primary.withOpacity(0.7)), - ) + Text( + '... +${widget.shift.durationDays! - 1} more days', + style: UiTypography.footnote2r.copyWith( + color: + UiColors.primary.withOpacity(0.7), + ), + ) ], ), ] else ...[ diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart index bb426fd7..81e6ac03 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart @@ -20,6 +20,119 @@ class _FindShiftsTabState extends State { String _searchQuery = ''; String _jobType = 'all'; + bool _isRecurring(Shift shift) => + (shift.orderType ?? '').toUpperCase() == 'RECURRING'; + + bool _isPermanent(Shift shift) => + (shift.orderType ?? '').toUpperCase() == 'PERMANENT'; + + DateTime? _parseShiftDate(String date) { + if (date.isEmpty) return null; + try { + return DateTime.parse(date); + } catch (_) { + return null; + } + } + + List _groupMultiDayShifts(List shifts) { + final Map> grouped = >{}; + for (final shift in shifts) { + if (!_isRecurring(shift) && !_isPermanent(shift)) { + continue; + } + final orderId = shift.orderId; + final roleId = shift.roleId; + if (orderId == null || roleId == null) { + continue; + } + final key = '$orderId::$roleId'; + grouped.putIfAbsent(key, () => []).add(shift); + } + + final Set addedGroups = {}; + final List result = []; + + for (final shift in shifts) { + if (!_isRecurring(shift) && !_isPermanent(shift)) { + result.add(shift); + continue; + } + final orderId = shift.orderId; + final roleId = shift.roleId; + if (orderId == null || roleId == null) { + result.add(shift); + continue; + } + final key = '$orderId::$roleId'; + if (addedGroups.contains(key)) { + continue; + } + addedGroups.add(key); + final List group = grouped[key] ?? []; + if (group.isEmpty) { + result.add(shift); + continue; + } + group.sort((a, b) { + final ad = _parseShiftDate(a.date); + final bd = _parseShiftDate(b.date); + if (ad == null && bd == null) return 0; + if (ad == null) return 1; + if (bd == null) return -1; + return ad.compareTo(bd); + }); + + final Shift first = group.first; + final List schedules = group + .map((s) => ShiftSchedule( + date: s.date, + startTime: s.startTime, + endTime: s.endTime, + )) + .toList(); + + result.add( + Shift( + id: first.id, + roleId: first.roleId, + title: first.title, + clientName: first.clientName, + logoUrl: first.logoUrl, + hourlyRate: first.hourlyRate, + location: first.location, + locationAddress: first.locationAddress, + date: first.date, + startTime: first.startTime, + endTime: first.endTime, + createdDate: first.createdDate, + tipsAvailable: first.tipsAvailable, + travelTime: first.travelTime, + mealProvided: first.mealProvided, + parkingAvailable: first.parkingAvailable, + gasCompensation: first.gasCompensation, + description: first.description, + instructions: first.instructions, + managers: first.managers, + latitude: first.latitude, + longitude: first.longitude, + status: first.status, + durationDays: schedules.length, + requiredSlots: first.requiredSlots, + filledSlots: first.filledSlots, + hasApplied: first.hasApplied, + totalValue: first.totalValue, + breakInfo: first.breakInfo, + orderId: first.orderId, + orderType: first.orderType, + schedules: schedules, + ), + ); + } + + return result; + } + Widget _buildFilterTab(String id, String label) { final isSelected = _jobType == id; return GestureDetector( @@ -49,8 +162,10 @@ class _FindShiftsTabState extends State { @override Widget build(BuildContext context) { + final groupedJobs = _groupMultiDayShifts(widget.availableJobs); + // Filter logic - final filteredJobs = widget.availableJobs.where((s) { + final filteredJobs = groupedJobs.where((s) { final matchesSearch = s.title.toLowerCase().contains(_searchQuery.toLowerCase()) || s.location.toLowerCase().contains(_searchQuery.toLowerCase()) || @@ -60,10 +175,15 @@ class _FindShiftsTabState extends State { if (_jobType == 'all') return true; if (_jobType == 'one-day') { + if (_isRecurring(s) || _isPermanent(s)) return false; return s.durationDays == null || s.durationDays! <= 1; } if (_jobType == 'multi-day') { - return s.durationDays != null && s.durationDays! > 1; + return _isRecurring(s) || + (s.durationDays != null && s.durationDays! > 1); + } + if (_jobType == 'long-term') { + return _isPermanent(s); } return true; }).toList(); diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/staff_shifts_module.dart b/apps/mobile/packages/features/staff/shifts/lib/src/staff_shifts_module.dart index 7d5b72a8..d9429238 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/staff_shifts_module.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/staff_shifts_module.dart @@ -66,6 +66,7 @@ class StaffShiftsModule extends Module { return ShiftsPage( initialTab: queryParams['tab'] ?? args?['initialTab'], selectedDate: args?['selectedDate'], + refreshAvailable: args?['refreshAvailable'] == true, ); }, ); diff --git a/apps/mobile/pubspec.lock b/apps/mobile/pubspec.lock index d9afe13f..9aa8910e 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -1530,4 +1530,4 @@ packages: version: "2.2.3" sdks: dart: ">=3.10.3 <4.0.0" - flutter: ">=3.38.4" + flutter: ">=3.38.4 <4.0.0" diff --git a/apps/mobile/pubspec.yaml b/apps/mobile/pubspec.yaml index 6ffcd99f..bca32555 100644 --- a/apps/mobile/pubspec.yaml +++ b/apps/mobile/pubspec.yaml @@ -3,6 +3,7 @@ publish_to: 'none' description: "A sample project using melos and modular scaffold." environment: sdk: '>=3.10.0 <4.0.0' + flutter: '>=3.38.0 <4.0.0' workspace: - packages/design_system - packages/core diff --git a/backend/dataconnect/connector/application/queries.gql b/backend/dataconnect/connector/application/queries.gql index 4a6d6396..d2a8a205 100644 --- a/backend/dataconnect/connector/application/queries.gql +++ b/backend/dataconnect/connector/application/queries.gql @@ -356,6 +356,95 @@ query getApplicationsByStaffId( } } +query getMyApplicationsByStaffId( + $staffId: UUID! + $offset: Int + $limit: Int + $dayStart: Timestamp + $dayEnd: Timestamp +) @auth(level: USER) { + applications( + where: { + staffId: { eq: $staffId } + status: { in: [ CONFIRMED, CHECKED_IN, CHECKED_OUT, LATE, PENDING] } + shift: { + date: { ge: $dayStart, le: $dayEnd } + } + + } + offset: $offset + limit: $limit + ) { + id + shiftId + staffId + status + appliedAt + checkInTime + checkOutTime + origin + createdAt + + shift { + id + title + date + startTime + endTime + location + status + durationDays + description + latitude + longitude + + order { + id + eventName + #location + + teamHub { + address + placeId + hubName + } + + business { + id + businessName + email + contactName + companyLogoUrl + } + vendor { + id + companyName + } + } + + } + + shiftRole { + id + roleId + count + assigned + startTime + endTime + hours + breakType + isBreakPaid + totalValue + role { + id + name + costPerHour + } + } + + } +} + query vaidateDayStaffApplication( $staffId: UUID! $offset: Int @@ -695,10 +784,14 @@ query listCompletedApplicationsByStaffId( durationDays latitude longitude + orderId order { id eventName + orderType + startDate + endDate teamHub { address diff --git a/backend/dataconnect/connector/shiftRole/queries.gql b/backend/dataconnect/connector/shiftRole/queries.gql index 07720bf0..6795e79d 100644 --- a/backend/dataconnect/connector/shiftRole/queries.gql +++ b/backend/dataconnect/connector/shiftRole/queries.gql @@ -254,7 +254,7 @@ query listShiftRolesByVendorId( shiftRoles( where: { shift: { - status: {in: [IN_PROGRESS, CONFIRMED, ASSIGNED, OPEN, PENDING]} #IN_PROGRESS? PENDING? + status: {in: [IN_PROGRESS, ASSIGNED, OPEN]} #IN_PROGRESS? order: { vendorId: { eq: $vendorId } } @@ -511,7 +511,7 @@ query getCompletedShiftsByBusinessId( shifts( where: { order: { businessId: { eq: $businessId } } - status: {in: [IN_PROGRESS, CONFIRMED, COMPLETED, OPEN]} + status: {in: [IN_PROGRESS, COMPLETED, OPEN]} date: { ge: $dateFrom, le: $dateTo } } offset: $offset diff --git a/backend/dataconnect/functions/seed.gql b/backend/dataconnect/functions/seed.gql index 8c6b69c0..1c6e0fcd 100644 --- a/backend/dataconnect/functions/seed.gql +++ b/backend/dataconnect/functions/seed.gql @@ -927,7 +927,7 @@ mutation seedAll @transaction { placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" latitude: 34.2611486 longitude: -118.5010287 - status: ASSIGNED + status: OPEN workersNeeded: 2 filled: 1 } @@ -950,7 +950,7 @@ mutation seedAll @transaction { placeId: "Eiw2ODAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" latitude: 34.2611486 longitude: -118.5010287 - status: ASSIGNED + status: OPEN workersNeeded: 2 filled: 1 } @@ -996,7 +996,7 @@ mutation seedAll @transaction { placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" latitude: 34.2611486 longitude: -118.5010287 - status: ASSIGNED + status: OPEN workersNeeded: 2 filled: 1 } @@ -1042,7 +1042,7 @@ mutation seedAll @transaction { placeId: "Eiw1MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" latitude: 34.2611486 longitude: -118.5010287 - status: ASSIGNED + status: OPEN workersNeeded: 2 filled: 1 } diff --git a/backend/dataconnect/schema/shift.gql b/backend/dataconnect/schema/shift.gql index 3e5f7c67..f03e54a7 100644 --- a/backend/dataconnect/schema/shift.gql +++ b/backend/dataconnect/schema/shift.gql @@ -1,9 +1,7 @@ enum ShiftStatus { DRAFT FILLED - PENDING ASSIGNED - CONFIRMED OPEN IN_PROGRESS COMPLETED diff --git a/docs/ARCHITECTURE/web-application/use-case.md b/docs/ARCHITECTURE/web-application/use-case.md index a4f65c95..e36a1ac6 100644 --- a/docs/ARCHITECTURE/web-application/use-case.md +++ b/docs/ARCHITECTURE/web-application/use-case.md @@ -11,9 +11,10 @@ This document details the primary business actions and user flows within the **K * **Description:** Secure entry into the management console. * **Main Flow:** 1. User enters email and password on the login screen. - 2. System verifies credentials. + 2. System verifies credentials against authentication service. 3. System determines user role (Admin, Client, or Vendor). - 4. User is directed to their specific role-based dashboard. + 4. User is directed to their specific role-based dashboard with customizable widgets. + 5. System loads user-specific dashboard layout preferences. --- @@ -21,67 +22,257 @@ This document details the primary business actions and user flows within the **K ### 2.1 Global Operational Oversight * **Actor:** Admin -* **Description:** Monitoring the pulse of the entire platform. -* **Main Flow:** User accesses Admin Dashboard -> Views all active orders across all clients -> Monitors user registration trends. - -### 2.2 Marketplace & Vendor Management -* **Actor:** Admin -* **Description:** Expanding the platform's supply network. +* **Description:** Monitoring the pulse of the entire platform through a customizable dashboard. * **Main Flow:** - 1. User navigates to Marketplace. - 2. User invites a new Vendor via email. - 3. User sets global default rates for roles. - 4. User audits vendor performance scores. + 1. User accesses Admin Dashboard with global metrics. + 2. Views fill rate, total spend, performance score, and active events. + 3. Monitors today's orders with status indicators (RAPID, Fully Staffed, Partial Staffed). + 4. Reviews action items prioritized by urgency (critical, high, medium). + 5. Accesses ecosystem visualization showing connections between Buyers, Enterprises, Sectors, Partners, and Vendors. + 6. Customizes dashboard widget layout via drag-and-drop. -### 2.3 System Administration +### 2.2 Vendor & Partner Management * **Actor:** Admin -* **Description:** Configuring platform-wide settings and security. -* **Main Flow:** User updates system configurations -> Reviews security audit logs -> Manages internal support tickets. +* **Description:** Managing the vendor network and partnerships. +* **Main Flow:** + 1. User navigates to Vendor Marketplace. + 2. Reviews vendor approval status and performance metrics. + 3. Sets vendor tier levels (Approved Vendor, Gold Vendor). + 4. Monitors vendor CSAT scores and compliance rates. + 5. Views vendor rate books and service rates. + +### 2.3 Order & Schedule Management +* **Actor:** Admin +* **Description:** Overseeing all orders across the platform. +* **Main Flow:** + 1. User views all orders with filtering by status (All, Upcoming, Active, Past, Conflicts). + 2. Reviews order details including business, hub, date/time, assigned staff. + 3. Monitors assignment status (Requested vs Assigned counts). + 4. Detects and resolves scheduling conflicts. + 5. Accesses schedule view for visual timeline. + +### 2.4 Workforce Management +* **Actor:** Admin +* **Description:** Managing platform-wide workforce. +* **Main Flow:** + 1. User navigates to Staff Directory. + 2. Views staff with filters (position, department, hub, profile type). + 3. Monitors compliance status (background checks, certifications). + 4. Reviews staff performance metrics (rating, reliability score, shift coverage). + 5. Manages onboarding workflows for new staff. + +### 2.5 Analytics & Reporting +* **Actor:** Admin +* **Description:** Generating insights through reports and activity logs. +* **Main Flow:** + 1. User accesses Reports Dashboard. + 2. Selects report type (Staffing Cost, Staff Performance, Operational Efficiency, Client Trends). + 3. Configures report parameters and filters. + 4. Views report insights with AI-generated recommendations. + 5. Exports reports in multiple formats (PDF, Excel, CSV). + 6. Reviews Activity Log for audit trail. --- ## 3. Client Executive Workflows -### 3.1 Strategic Insights (Savings Engine) +### 3.1 Dashboard Overview * **Actor:** Client Executive -* **Description:** Using AI to optimize labor spend. +* **Description:** Personalized dashboard for order and labor management. * **Main Flow:** - 1. User opens the Savings Engine. - 2. User reviews identified cost-saving opportunities. - 3. User clicks "Approve Strategy" to implement recommendations (e.g., vendor consolidation). + 1. User opens Client Dashboard with customizable widgets. + 2. Views action items (overdue invoices, unfilled orders, rapid requests). + 3. Monitors key metrics (Today's Count, In Progress, Needs Attention). + 4. Reviews labor summary with cost breakdown by position. + 5. Analyzes sales analytics via pie charts. -### 3.2 Finance & Billing Management -* **Actor:** Client Executive / Finance Admin -* **Description:** Managing corporate financial obligations. -* **Main Flow:** User views all pending invoices -> Downloads detailed line-item reports -> Processes payments to Krow. +### 3.2 Order Management +* **Actor:** Client Executive / Operations Manager +* **Description:** Creating and managing staffing requests. +* **Main Flow:** + 1. User clicks "Order Now" or "RAPID Order" for urgent requests. + 2. Selects business, hub, and event details. + 3. Defines shifts with roles, counts, start/end times, and rates. + 4. Chooses order type (one-time, rapid, recurring, permanent). + 5. Enables conflict detection to prevent scheduling issues. + 6. Reviews detected conflicts before submission. + 7. Submits order to preferred vendor or marketplace. -### 3.3 Operations Overview +### 3.3 Vendor Discovery & Selection * **Actor:** Client Executive -* **Description:** High-level monitoring of venue operations. -* **Main Flow:** User views a summary of their venue orders -> Reviews ratings of assigned staff -> Monitors fulfillment rates. +* **Description:** Finding and managing vendor relationships. +* **Main Flow:** + 1. User navigates to Vendor Marketplace. + 2. Searches and filters vendors by region, category, rating, price. + 3. Views vendor profiles with metrics (staff count, rating, fill rate, response time). + 4. Expands vendor cards to view rate books by category. + 5. Sets preferred vendor for automatic order routing. + 6. Configures vendor preferences (locked vendors for optimization). + 7. Contacts vendors via integrated messaging. + +### 3.4 Savings Engine (Strategic Insights) +* **Actor:** Client Executive +* **Description:** Using AI to optimize labor spend and vendor mix. +* **Main Flow:** + 1. User opens Savings Engine. + 2. Reviews overview cards showing total spend, potential savings, fill rate. + 3. Selects analysis timeframe (7 days, 30 days, Quarter, Year). + 4. Navigates tabs for different insights: + - **Overview**: Dynamic dashboard with savings opportunities + - **Budget**: Budget utilization tracker + - **Strategies**: Smart operation strategies with AI recommendations + - **Predictions**: Cost forecasts and trend analysis + - **Vendors**: Vendor performance comparison + 5. Views actionable strategies (vendor consolidation, rate optimization). + 6. Exports analysis report. + +### 3.5 Finance & Invoicing +* **Actor:** Client Executive / Finance Admin +* **Description:** Managing invoices and payments. +* **Main Flow:** + 1. User views invoice list filtered by status (Open, Overdue, Paid, Disputed). + 2. Opens invoice detail to review line items by role and staff. + 3. Views from/to company information and payment terms. + 4. Downloads invoice in PDF or Excel format. + 5. Processes payment or disputes invoice with reason. + 6. Tracks payment history. + +### 3.6 Communication & Support +* **Actor:** Client Executive +* **Description:** Engaging with vendors and getting help. +* **Main Flow:** + 1. User accesses Message Center for conversations. + 2. Initiates conversation with vendors or admins. + 3. Views conversation threads grouped by type (client-vendor, client-admin). + 4. Accesses Tutorials for platform guidance. + 5. Submits support tickets via Support Center. --- ## 4. Vendor Workflows (Staffing Agency) -### 4.1 Vendor Operations (Order Fulfillment) +### 4.1 Vendor Dashboard +* **Actor:** Vendor Manager +* **Description:** Comprehensive view of operations and performance. +* **Main Flow:** + 1. User accesses Vendor Dashboard with customizable widgets. + 2. Views KPI cards (Orders Today, In Progress, RAPID, Staff Assigned). + 3. Monitors action items (urgent unfilled orders, expiring certifications, invoices to submit). + 4. Reviews recent orders table with assignment status. + 5. Accesses revenue carousel showing monthly revenue, total revenue, active orders. + 6. Views top clients by revenue and order count. + 7. Reviews client loyalty status (Champion, Loyal, At Risk). + 8. Monitors top performer staff by rating. + +### 4.2 Order Fulfillment * **Actor:** Vendor Manager * **Description:** Fulfilling client staffing requests. * **Main Flow:** - 1. User views incoming shift requests. - 2. User selects a shift. - 3. User uses the **Worker Selection Tool** to assign the best-fit staff. - 4. User confirms assignment. + 1. User views incoming orders via "Orders" section. + 2. Filters orders by tab (All, Conflicts, Upcoming, Active, Past). + 3. Reviews order details (business, hub, event, date/time, roles). + 4. Identifies RAPID orders (< 24 hours) needing immediate attention. + 5. Clicks "Assign Staff" to open Smart Assign Modal. + 6. Selects optimal staff based on skills, availability, and proximity. + 7. Confirms assignments and updates order status. + 8. Reviews conflict alerts for staff/venue overlaps. -### 4.2 Workforce Roster Management +### 4.3 Workforce Roster Management * **Actor:** Vendor Manager -* **Description:** Maintaining their agency's supply of workers. -* **Main Flow:** User navigates to Roster -> Adds new workers -> Updates compliance documents and certifications -> Edits worker profiles. +* **Description:** Managing agency's worker pool. +* **Main Flow:** + 1. User navigates to Staff Directory. + 2. Views staff with filtering options (profile type, position, department, hub). + 3. Toggles between grid and list view. + 4. Adds new staff via "Add Staff" button. + 5. Fills staff profile form (personal info, position, department, hub, contact). + 6. Edits existing staff profiles. + 7. Monitors staff metrics (rating, reliability score, shift coverage, cancellations). + 8. Reviews compliance status (background checks, certifications). -### 4.3 Vendor Finance +### 4.4 Staff Onboarding * **Actor:** Vendor Manager -* **Description:** Managing agency revenue and worker payouts. -* **Main Flow:** User views payout history -> Submits invoices for completed shifts -> Tracks pending payments from Krow. +* **Description:** Streamlined multi-step onboarding for new workers. +* **Main Flow:** + 1. User navigates to "Onboard Staff" section. + 2. Completes profile setup step (name, email, position, department). + 3. Uploads required documents (ID, certifications, licenses). + 4. Assigns training modules. + 5. Reviews completion status. + 6. Activates staff member upon completion. + +### 4.5 Compliance Management +* **Actor:** Vendor Manager +* **Description:** Maintaining workforce compliance standards. +* **Main Flow:** + 1. User accesses Compliance Dashboard. + 2. Views compliance metrics (background check status, certification expiry). + 3. Filters staff needing attention. + 4. Updates compliance documents in Document Vault. + 5. Tracks certification renewal deadlines. + +### 4.6 Schedule & Availability +* **Actor:** Vendor Manager +* **Description:** Managing staff availability and schedules. +* **Main Flow:** + 1. User navigates to Staff Availability. + 2. Views calendar-based availability grid. + 3. Updates staff availability preferences. + 4. Accesses Schedule view for visual timeline of assignments. + 5. Identifies gaps and conflicts. + +### 4.7 Client Relationship Management +* **Actor:** Vendor Manager +* **Description:** Managing client accounts and preferences. +* **Main Flow:** + 1. User navigates to Clients section. + 2. Views client list with business details. + 3. Adds new client accounts. + 4. Edits client information (contact, address, hubs, departments). + 5. Configures client preferences (favorite staff, blocked staff). + 6. Sets ERP integration details (vendor ID, cost center, EDI format). + +### 4.8 Rate Management +* **Actor:** Vendor Manager +* **Description:** Managing service rates and pricing. +* **Main Flow:** + 1. User accesses Service Rates section. + 2. Views rate cards by client and role. + 3. Creates new rate entries (role, client rate, employee wage). + 4. Configures markup percentage and vendor fee. + 5. Sets approved cap rates. + 6. Activates/deactivates rates. + +### 4.9 Vendor Finance & Invoicing +* **Actor:** Vendor Manager +* **Description:** Managing revenue and submitting invoices. +* **Main Flow:** + 1. User views invoice list for completed orders. + 2. Auto-generates invoices from completed events. + 3. Reviews invoice details with staff entries and line items. + 4. Edits invoice before submission if needed. + 5. Submits invoice to client. + 6. Tracks invoice status (Draft, Open, Confirmed, Paid). + 7. Downloads invoice for records. + +### 4.10 Performance Analytics +* **Actor:** Vendor Manager +* **Description:** Monitoring vendor performance metrics. +* **Main Flow:** + 1. User accesses Performance section. + 2. Reviews fill rate, on-time performance, client satisfaction. + 3. Views staff performance leaderboard. + 4. Analyzes revenue trends by client and timeframe. + +### 4.11 Savings Engine (Growth Opportunities) +* **Actor:** Vendor Manager +* **Description:** Identifying growth and optimization opportunities. +* **Main Flow:** + 1. User opens Savings Engine with vendor-specific tabs. + 2. Reviews performance metrics and benchmarks. + 3. Identifies opportunities to improve ratings and win more business. + 4. Views workforce utilization statistics. + 5. Analyzes growth forecasts. --- @@ -90,16 +281,181 @@ This document details the primary business actions and user flows within the **K ### 5.1 Order Details & History * **Actor:** All Roles * **Description:** Accessing granular data for any specific staffing request. -* **Main Flow:** User clicks any order ID -> System displays shift times, roles, assigned staff, and audit history. +* **Main Flow:** + 1. User clicks any order ID from lists or dashboards. + 2. System displays comprehensive order information: + - Event details (name, business, hub, date, time) + - Shift configuration with roles, counts, and rates + - Assigned staff with profiles + - Status history and audit trail + - Detected conflicts (if any) + - Invoice linkage (if completed) + 3. User can edit order (if permissions allow). + 4. User can assign/reassign staff. + 5. User can view related invoices. ### 5.2 Invoice Detail View * **Actor:** Admin, Client, Vendor * **Description:** Reviewing the breakdown of costs for a billing period. -* **Main Flow:** User opens an invoice -> System displays worker names, hours worked, bill rates, and total totals per role. +* **Main Flow:** + 1. User opens an invoice from the invoice list. + 2. System displays invoice header (invoice number, dates, status, parties). + 3. Views detailed breakdown: + - Roles section with staff entries per role + - Hours worked (regular, overtime, double-time) + - Bill rates and totals per role + - Additional charges + - Subtotal and grand total + 4. Reviews payment terms and PO reference. + 5. Downloads invoice in PDF or Excel. + 6. Copies invoice data to clipboard. + 7. Sends invoice via email (vendor role). + 8. Approves or disputes invoice (client role). + +### 5.3 Task Board +* **Actor:** All Roles +* **Description:** Collaborative task management across teams. +* **Main Flow:** + 1. User accesses Task Board. + 2. Views tasks in columns by status (Pending, In Progress, On Hold, Completed). + 3. Drags tasks between columns to update status. + 4. Creates new tasks with details (name, description, priority, due date). + 5. Assigns tasks to team members. + 6. Adds comments and attachments to tasks. + 7. Filters tasks by department, priority, or assignee. + +### 5.4 Message Center +* **Actor:** All Roles +* **Description:** Cross-platform communication hub. +* **Main Flow:** + 1. User accesses Message Center. + 2. Views conversation list with unread counts. + 3. Filters by conversation type (client-vendor, client-admin, internal). + 4. Opens conversation thread. + 5. Sends messages with attachments. + 6. Views system-generated messages for automated events. + 7. Archives completed conversations. + +### 5.5 Reports & Analytics +* **Actor:** All Roles (with role-specific access) +* **Description:** Data-driven insights and custom reporting. +* **Main Flow:** + 1. User accesses Reports Dashboard. + 2. Selects from report types: + - Staffing Cost Report + - Staff Performance Report + - Operational Efficiency Report + - Client Trends Report + - Custom Report Builder + 3. Configures report parameters (date range, filters, grouping). + 4. Views AI-generated insights banner with key findings. + 5. Exports report in preferred format. + 6. Schedules recurring reports for automated delivery. + 7. Saves report templates for reuse. + +### 5.6 Teams Management +* **Actor:** Admin, Client, Vendor +* **Description:** Creating and managing staff teams. +* **Main Flow:** + 1. User navigates to Teams section. + 2. Views team list with member counts. + 3. Creates new team with name and description. + 4. Adds team members from staff directory. + 5. Views team detail page with member profiles. + 6. Assigns teams to orders as groups. + +### 5.7 Staff Conflict Detection +* **Actor:** Admin, Vendor +* **Description:** Automated detection of scheduling conflicts. +* **Main Flow:** + 1. System automatically detects conflicts when creating/editing orders: + - **Staff Overlap**: Same staff assigned to overlapping shifts + - **Venue Overlap**: Same venue booked for overlapping times + - **Time Buffer**: Insufficient travel time between assignments + 2. System assigns severity level (Critical, High, Medium, Low). + 3. Displays conflict alerts with details (conflicting event, staff, location). + 4. User resolves conflicts before finalizing order. + 5. System tracks conflict resolution in audit log. + +### 5.8 Dashboard Customization +* **Actor:** All Roles +* **Description:** Personalizing dashboard layouts. +* **Main Flow:** + 1. User clicks "Customize Dashboard" button. + 2. Enters customization mode with drag-and-drop interface. + 3. Reorders widgets by dragging. + 4. Hides/shows widgets using visibility controls. + 5. Previews changes in real-time. + 6. Saves layout preferences to user profile. + 7. Resets to default layout if desired. + +--- + +## 6. Advanced Features + +### 6.1 Smart Assignment Engine (Vendor) +* **Actor:** Vendor Manager +* **Description:** AI-powered staff assignment optimization. +* **Main Flow:** + 1. User clicks "Smart Assign" on an order. + 2. System analyzes requirements (skills, location, time, availability). + 3. Engine scores available staff based on: + - Skill match + - Proximity to venue + - Past performance + - Availability + - Client preferences + 4. Presents ranked staff recommendations. + 5. User reviews suggestions and confirms assignments. + +### 6.2 Auto-Invoice Generation +* **Actor:** Vendor Manager +* **Description:** Automated invoice creation from completed orders. +* **Main Flow:** + 1. When order status changes to "Completed", system triggers auto-invoice. + 2. System aggregates staff entries, hours, and rates. + 3. Generates invoice line items by role. + 4. Calculates totals (regular, overtime, double-time). + 5. Applies additional charges if configured. + 6. Creates draft invoice for vendor review. + 7. Vendor reviews and submits to client. + +### 6.3 Vendor Preferences & Optimization (Client) +* **Actor:** Client Executive +* **Description:** Configuring vendor routing and procurement strategies. +* **Main Flow:** + 1. User accesses Client Vendor Preferences panel. + 2. Sets preferred vendor for automatic order routing. + 3. Configures locked vendors (never used for optimization). + 4. Enables/disables procurement optimization. + 5. System respects preferences when suggesting vendors in Savings Engine. + +### 6.4 Contract Conversion & Tier Optimization +* **Actor:** Admin, Client (via Savings Engine) +* **Description:** Analyzing opportunities to move spend to preferred vendors. +* **Main Flow:** + 1. User accesses "Conversion Map" tab in Savings Engine. + 2. Views non-contracted spend by vendor. + 3. System identifies conversion opportunities to approved/gold vendors. + 4. Reviews potential savings from rate arbitrage. + 5. Approves conversion strategy. + 6. System routes future orders accordingly. + +### 6.5 Predictive Savings Model +* **Actor:** Admin, Client +* **Description:** Forecasting cost savings through AI analysis. +* **Main Flow:** + 1. User accesses "Predictions" tab in Savings Engine. + 2. System analyzes historical spend, rates, and vendor performance. + 3. Generates forecasts for 7 days, 30 days, quarter, year. + 4. Identifies rate optimization opportunities. + 5. Recommends vendor consolidation strategies. + 6. Shows projected ROI for each strategy. --- # Use Case Diagram + ```mermaid flowchart TD subgraph AccessControl [Access & Authentication] @@ -118,53 +474,166 @@ flowchart TD subgraph AdminWorkflows [Admin Workflows] AdminDash --> GlobalOversight[Global Oversight] + GlobalOversight --> EcosystemWheel[Ecosystem Wheel] GlobalOversight --> ViewAllOrders[View All Orders] - GlobalOversight --> ViewAllUsers[View All Users] + GlobalOversight --> ActionItems[Action Items] - AdminDash --> MarketplaceMgmt[Marketplace Management] - MarketplaceMgmt --> OnboardVendor[Onboard Vendor] - MarketplaceMgmt --> ManageRates[Manage Global Rates] + AdminDash --> VendorMgmt[Vendor Management] + VendorMgmt --> ApproveVendors[Approve Vendors] + VendorMgmt --> SetTiers[Set Vendor Tiers] - AdminDash --> SystemAdmin[System Administration] - SystemAdmin --> ConfigSettings[Configure Settings] - SystemAdmin --> AuditLogs[View Audit Logs] + AdminDash --> WorkforceMgmt[Workforce Management] + WorkforceMgmt --> StaffDirectory[Staff Directory] + WorkforceMgmt --> Compliance[Compliance Dashboard] + + AdminDash --> AnalyticsReports[Analytics & Reports] + AnalyticsReports --> ReportsDashboard[Reports Dashboard] + AnalyticsReports --> ActivityLog[Activity Log] end subgraph ClientWorkflows [Client Executive Workflows] - ClientDash --> ClientInsights[Strategic Insights] - ClientInsights --> SavingsEngine[Savings Engine] - SavingsEngine --> ViewOpp[View Opportunity] - ViewOpp --> ApproveStrategy[Approve Strategy] + ClientDash --> ClientActionItems[Action Items] + ClientActionItems --> ReviewAlerts[Review Alerts] - ClientDash --> ClientFinance[Finance & Billing] + ClientDash --> OrderMgmt[Order Management] + OrderMgmt --> CreateOrder[Create Order] + CreateOrder --> DefineShifts[Define Shifts & Roles] + DefineShifts --> ConflictDetection[Conflict Detection] + ConflictDetection --> SubmitOrder[Submit Order] + + OrderMgmt --> ViewMyOrders[View My Orders] + ViewMyOrders --> OrderDetail[Order Detail] + + ClientDash --> VendorDiscovery[Vendor Discovery] + VendorDiscovery --> BrowseMarketplace[Browse Marketplace] + BrowseMarketplace --> SetPreferred[Set Preferred Vendor] + BrowseMarketplace --> ContactVendor[Contact Vendor] + + ClientDash --> SavingsEngine[Savings Engine] + SavingsEngine --> AnalyzeSpend[Analyze Spend] + AnalyzeSpend --> ViewStrategies[View Strategies] + ViewStrategies --> ApproveStrategy[Approve Strategy] + SavingsEngine --> PredictiveSavings[Predictive Savings] + SavingsEngine --> ConversionMap[Conversion Map] + + ClientDash --> ClientFinance[Finance & Invoicing] ClientFinance --> ViewInvoices[View Invoices] - ClientFinance --> PayInvoice[Pay Invoice] + ViewInvoices --> InvoiceDetail[Invoice Detail] + InvoiceDetail --> PayInvoice[Pay Invoice] + InvoiceDetail --> DisputeInvoice[Dispute Invoice] - ClientDash --> ClientOps[Operations Overview] - ClientOps --> ViewMyOrders[View My Orders] - ClientOps --> ViewMyStaff[View Assigned Staff] + ClientDash --> Communication[Communication] + Communication --> MessageCenter[Message Center] + Communication --> SupportCenter[Support Center] end subgraph VendorWorkflows [Vendor Workflows] - VendorDash --> VendorOps[Vendor Operations] - VendorOps --> ViewRequests[View Shift Requests] - ViewRequests --> AssignWorker[Assign Worker] - VendorOps --> ManageRoster[Manage Worker Roster] - ManageRoster --> UpdateWorkerProfile[Update Worker Profile] + VendorDash --> VendorKPIs[KPI Dashboard] + VendorKPIs --> RevenueStats[Revenue Stats] + VendorKPIs --> TopClients[Top Clients] + VendorKPIs --> TopPerformers[Top Performers] - VendorDash --> VendorFinance[Vendor Finance] - VendorFinance --> ViewPayouts[View Payouts] + VendorDash --> OrderFulfillment[Order Fulfillment] + OrderFulfillment --> ViewOrders[View Orders] + ViewOrders --> FilterOrders[Filter Orders] + FilterOrders --> AssignStaff[Smart Assign Staff] + AssignStaff --> ResolveConflicts[Resolve Conflicts] + + VendorDash --> RosterMgmt[Roster Management] + RosterMgmt --> StaffDir[Staff Directory] + StaffDir --> AddStaff[Add Staff] + StaffDir --> EditStaff[Edit Staff] + StaffDir --> ViewMetrics[View Staff Metrics] + + RosterMgmt --> OnboardStaff[Onboard Staff] + OnboardStaff --> ProfileSetup[Profile Setup] + ProfileSetup --> UploadDocs[Upload Documents] + UploadDocs --> AssignTraining[Assign Training] + AssignTraining --> ActivateStaff[Activate Staff] + + VendorDash --> ComplianceMgmt[Compliance Management] + ComplianceMgmt --> ComplianceDash[Compliance Dashboard] + ComplianceDash --> DocumentVault[Document Vault] + ComplianceDash --> CertTracking[Certification Tracking] + + VendorDash --> ScheduleAvail[Schedule & Availability] + ScheduleAvail --> StaffAvailability[Staff Availability] + ScheduleAvail --> ScheduleView[Schedule View] + + VendorDash --> ClientMgmt[Client Management] + ClientMgmt --> ManageClients[Manage Clients] + ManageClients --> ClientPrefs[Client Preferences] + + VendorDash --> RateMgmt[Rate Management] + RateMgmt --> ServiceRates[Service Rates] + ServiceRates --> RateCards[Rate Cards] + + VendorDash --> VendorFinance[Finance] + VendorFinance --> AutoInvoice[Auto-Generate Invoice] VendorFinance --> SubmitInvoice[Submit Invoice] + VendorFinance --> TrackPayments[Track Payments] + + VendorDash --> VendorPerformance[Performance Analytics] + VendorPerformance --> FillRate[Fill Rate] + VendorPerformance --> CSAT[Client Satisfaction] + VendorPerformance --> RevenueAnalysis[Revenue Analysis] end subgraph SharedModules [Shared Functional Modules] - ViewAllOrders -.-> OrderDetail[Order Details] - ViewMyOrders -.-> OrderDetail - ViewRequests -.-> OrderDetail + TaskBoard[Task Board] -.-> Tasks[Manage Tasks] + Tasks -.-> DragDrop[Drag & Drop Status] - AssignWorker -.-> WorkerSelection[Worker Selection Tool] + MessageCenter -.-> Conversations[Conversations] + Conversations -.-> SendMessage[Send Message] - ViewInvoices -.-> InvoiceDetail[Invoice Detail View] - SubmitInvoice -.-> InvoiceDetail + ReportsDashboard -.-> ReportTypes[Report Types] + ReportTypes -.-> CustomBuilder[Custom Report Builder] + ReportTypes -.-> ScheduledReports[Scheduled Reports] + ReportTypes -.-> ExportReport[Export Report] + + TeamsModule[Teams] -.-> CreateTeam[Create Team] + CreateTeam -.-> AddMembers[Add Members] + + DashboardCustom[Dashboard Customization] -.-> DragWidgets[Drag Widgets] + DragWidgets -.-> HideShow[Hide/Show Widgets] + HideShow -.-> SaveLayout[Save Layout] end ``` + +--- + +## Summary of Key Enhancements + +**Compared to the original document, this updated version includes:** + +1. **Detailed Dashboard Workflows**: Comprehensive descriptions of customizable dashboards for each role with specific widgets and metrics. + +2. **Advanced Order Management**: Multi-step order creation with shift configuration, conflict detection, and order type options (one-time, rapid, recurring, permanent). + +3. **Smart Assignment**: AI-powered staff assignment engine for vendors to optimize worker selection. + +4. **Savings Engine**: Detailed AI-driven cost optimization workflows with predictive modeling, vendor conversion strategies, and budget tracking. + +5. **Vendor Marketplace**: Complete vendor discovery and selection process with filtering, rate comparison, and preference settings. + +6. **Enhanced Finance**: Auto-invoice generation, detailed invoice views, export capabilities, and dispute resolution. + +7. **Onboarding Workflow**: Multi-step staff onboarding process for vendors. + +8. **Compliance Management**: Dedicated compliance dashboard and document vault. + +9. **Conflict Detection**: Automated scheduling conflict detection with severity levels. + +10. **Communication Hub**: Integrated message center for cross-platform communication. + +11. **Teams Management**: Team creation and assignment workflows. + +12. **Advanced Analytics**: Multiple report types, custom report builder, scheduled reports, and AI-generated insights. + +13. **Dashboard Customization**: Drag-and-drop widget management with layout persistence. + +14. **Schedule & Availability**: Calendar-based staff availability management with visual schedule view. + +15. **Client & Rate Management**: Vendor-side client relationship and service rate management. + +This document now accurately reflects the robust feature set implemented in the krow_web_application.