Merge branch 'dev' into feature/session-persistence-424

This commit is contained in:
2026-02-20 20:56:18 +05:30
committed by GitHub
22 changed files with 1296 additions and 132 deletions

View File

@@ -29,10 +29,10 @@ jobs:
id: detect id: detect
run: | run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then 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 }}" 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...HEAD 2>/dev/null || echo "")
CHANGED_FILES=$(git diff --name-only origin/$BASE_REF..origin/$HEAD_REF 2>/dev/null || echo "")
else else
# For push, compare with previous commit # For push, compare with previous commit
if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then
@@ -74,40 +74,48 @@ jobs:
- name: 🦋 Set up Flutter - name: 🦋 Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.19.x' flutter-version: '3.38.x'
channel: 'stable' channel: 'stable'
cache: true cache: true
- name: 🔧 Install Firebase CLI
run: |
npm install -g firebase-tools
- name: 📦 Get Flutter dependencies - name: 📦 Get Flutter dependencies
run: | run: |
cd apps/mobile make mobile-install
flutter pub get
- name: 🔨 Run compilation check - name: 🔨 Run compilation check
run: | run: |
cd apps/mobile set -o pipefail
echo "⚙️ Running build_runner..."
flutter pub run build_runner build --delete-conflicting-outputs 2>&1 || true
echo "" echo "🏗️ Building client app for Android (dev mode)..."
echo "🔬 Running flutter analyze on all files..." if ! make mobile-client-build PLATFORM=apk MODE=debug 2>&1 | tee client_build.txt; then
flutter analyze lib/ --no-fatal-infos 2>&1 | tee analyze_output.txt || true echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "❌ CLIENT APP BUILD FAILED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 1
fi
echo "" echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Check for actual errors (not just warnings) echo "🏗️ Building staff app for Android (dev mode)..."
if grep -E "^\s*(error|SEVERE):" analyze_output.txt > /dev/null; then if ! make mobile-staff-build PLATFORM=apk MODE=debug 2>&1 | tee staff_build.txt; then
echo "❌ COMPILATION ERRORS FOUND:" echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
grep -B 2 -A 1 -E "^\s*(error|SEVERE):" analyze_output.txt | sed 's/^/ /' echo "❌ STAFF APP BUILD FAILED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 1 exit 1
else
echo "✅ Compilation check PASSED - No errors found"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
fi fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Build check PASSED - Both apps built successfully"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
lint: lint:
name: 🧹 Lint Changed Files name: 🧹 Lint Changed Files
runs-on: macos-latest runs-on: macos-latest
@@ -120,18 +128,21 @@ jobs:
- name: 🦋 Set up Flutter - name: 🦋 Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.19.x' flutter-version: '3.38.x'
channel: 'stable' channel: 'stable'
cache: true cache: true
- name: 🔧 Install Firebase CLI
run: |
npm install -g firebase-tools
- name: 📦 Get Flutter dependencies - name: 📦 Get Flutter dependencies
run: | run: |
cd apps/mobile make mobile-install
flutter pub get
- name: 🔍 Lint changed Dart files - name: 🔍 Lint changed Dart files
run: | run: |
cd apps/mobile set -o pipefail
# Get the list of changed files # Get the list of changed files
CHANGED_FILES="${{ needs.detect-changes.outputs.changed-files }}" CHANGED_FILES="${{ needs.detect-changes.outputs.changed-files }}"
@@ -153,7 +164,7 @@ jobs:
if [[ -n "$file" && "$file" == *.dart && -f "$file" ]]; then if [[ -n "$file" && "$file" == *.dart && -f "$file" ]]; then
echo "📝 Analyzing: $file" 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 HAS_ERRORS=true
FAILED_FILES+=("$file") FAILED_FILES+=("$file")
fi fi

View File

@@ -98,7 +98,11 @@ extension StaffNavigator on IModularNavigator {
/// Parameters: /// Parameters:
/// * [selectedDate] - Optional date to pre-select in the shifts view /// * [selectedDate] - Optional date to pre-select in the shifts view
/// * [initialTab] - Optional initial tab (via query parameter) /// * [initialTab] - Optional initial tab (via query parameter)
void toShifts({DateTime? selectedDate, String? initialTab}) { void toShifts({
DateTime? selectedDate,
String? initialTab,
bool? refreshAvailable,
}) {
final Map<String, dynamic> args = <String, dynamic>{}; final Map<String, dynamic> args = <String, dynamic>{};
if (selectedDate != null) { if (selectedDate != null) {
args['selectedDate'] = selectedDate; args['selectedDate'] = selectedDate;
@@ -106,6 +110,9 @@ extension StaffNavigator on IModularNavigator {
if (initialTab != null) { if (initialTab != null) {
args['initialTab'] = initialTab; args['initialTab'] = initialTab;
} }
if (refreshAvailable == true) {
args['refreshAvailable'] = true;
}
navigate( navigate(
StaffPaths.shifts, StaffPaths.shifts,
arguments: args.isEmpty ? null : args, arguments: args.isEmpty ? null : args,

View File

@@ -2,6 +2,38 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/shifts/break/break.dart'; import 'package:krow_domain/src/entities/shifts/break/break.dart';
class Shift extends Equatable { 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<ShiftManager>? 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<ShiftSchedule>? schedules;
const Shift({ const Shift({
required this.id, required this.id,
@@ -33,6 +65,9 @@ class Shift extends Equatable {
this.hasApplied, this.hasApplied,
this.totalValue, this.totalValue,
this.breakInfo, this.breakInfo,
this.orderId,
this.orderType,
this.schedules,
}); });
final String id; final String id;
final String title; final String title;
@@ -95,9 +130,27 @@ class Shift extends Equatable {
hasApplied, hasApplied,
totalValue, totalValue,
breakInfo, 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<Object?> get props => <Object?>[date, startTime, endTime];
}
class ShiftManager extends Equatable { class ShiftManager extends Equatable {
const ShiftManager({required this.name, required this.phone, this.avatar}); const ShiftManager({required this.name, required this.phone, this.avatar});

View File

@@ -105,7 +105,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
.state(hub.state) .state(hub.state)
.street(hub.street) .street(hub.street)
.country(hub.country) .country(hub.country)
.status(dc.ShiftStatus.CONFIRMED) .status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded) .workersNeeded(workersNeeded)
.filled(0) .filled(0)
.durationDays(1) .durationDays(1)
@@ -224,7 +224,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
.state(hub.state) .state(hub.state)
.street(hub.street) .street(hub.street)
.country(hub.country) .country(hub.country)
.status(dc.ShiftStatus.CONFIRMED) .status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded) .workersNeeded(workersNeeded)
.filled(0) .filled(0)
.durationDays(1) .durationDays(1)
@@ -342,7 +342,7 @@ class ClientCreateOrderRepositoryImpl implements ClientCreateOrderRepositoryInte
.state(hub.state) .state(hub.state)
.street(hub.street) .street(hub.street)
.country(hub.country) .country(hub.country)
.status(dc.ShiftStatus.CONFIRMED) .status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded) .workersNeeded(workersNeeded)
.filled(0) .filled(0)
.durationDays(1) .durationDays(1)

View File

@@ -20,6 +20,42 @@ class PermanentOrderView extends StatelessWidget {
/// Creates a [PermanentOrderView]. /// Creates a [PermanentOrderView].
const PermanentOrderView({super.key}); const PermanentOrderView({super.key});
DateTime _firstPermanentShiftDate(
DateTime startDate,
List<String> permanentDays,
) {
final DateTime start = DateTime(startDate.year, startDate.month, startDate.day);
final DateTime end = start.add(const Duration(days: 29));
final Set<String> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TranslationsClientCreateOrderPermanentEn labels = final TranslationsClientCreateOrderPermanentEn labels =
@@ -42,6 +78,10 @@ class PermanentOrderView extends StatelessWidget {
}, },
builder: (BuildContext context, PermanentOrderState state) { builder: (BuildContext context, PermanentOrderState state) {
if (state.status == PermanentOrderStatus.success) { if (state.status == PermanentOrderStatus.success) {
final DateTime initialDate = _firstPermanentShiftDate(
state.startDate,
state.permanentDays,
);
return PermanentOrderSuccessView( return PermanentOrderSuccessView(
title: labels.title, title: labels.title,
message: labels.subtitle, message: labels.subtitle,
@@ -50,7 +90,7 @@ class PermanentOrderView extends StatelessWidget {
ClientPaths.orders, ClientPaths.orders,
(_) => false, (_) => false,
arguments: <String, dynamic>{ arguments: <String, dynamic>{
'initialDate': state.startDate.toIso8601String(), 'initialDate': initialDate.toIso8601String(),
}, },
), ),
); );

View File

@@ -20,6 +20,43 @@ class RecurringOrderView extends StatelessWidget {
/// Creates a [RecurringOrderView]. /// Creates a [RecurringOrderView].
const RecurringOrderView({super.key}); const RecurringOrderView({super.key});
DateTime _firstRecurringShiftDate(
DateTime startDate,
DateTime endDate,
List<String> recurringDays,
) {
final DateTime start = DateTime(startDate.year, startDate.month, startDate.day);
final DateTime end = DateTime(endDate.year, endDate.month, endDate.day);
final Set<String> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TranslationsClientCreateOrderRecurringEn labels = final TranslationsClientCreateOrderRecurringEn labels =
@@ -44,6 +81,15 @@ class RecurringOrderView extends StatelessWidget {
}, },
builder: (BuildContext context, RecurringOrderState state) { builder: (BuildContext context, RecurringOrderState state) {
if (state.status == RecurringOrderStatus.success) { 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( return RecurringOrderSuccessView(
title: labels.title, title: labels.title,
message: labels.subtitle, message: labels.subtitle,
@@ -52,7 +98,7 @@ class RecurringOrderView extends StatelessWidget {
ClientPaths.orders, ClientPaths.orders,
(_) => false, (_) => false,
arguments: <String, dynamic>{ arguments: <String, dynamic>{
'initialDate': state.startDate.toIso8601String(), 'initialDate': initialDate.toIso8601String(),
}, },
), ),
); );

View File

@@ -265,7 +265,7 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
.state(selectedHub.state) .state(selectedHub.state)
.street(selectedHub.street) .street(selectedHub.street)
.country(selectedHub.country) .country(selectedHub.country)
.status(dc.ShiftStatus.PENDING) .status(dc.ShiftStatus.OPEN)
.workersNeeded(workersNeeded) .workersNeeded(workersNeeded)
.filled(0) .filled(0)
.durationDays(1) .durationDays(1)

View File

@@ -46,6 +46,145 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
Future<List<Shift>> getHistoryShifts() async { Future<List<Shift>> getHistoryShifts() async {
final staffId = await _service.getStaffId(); final staffId = await _service.getStaffId();
return _connectorRepository.getHistoryShifts(staffId: staffId); return _connectorRepository.getHistoryShifts(staffId: staffId);
final fdc.QueryResult<dc.ListCompletedApplicationsByStaffIdData, dc.ListCompletedApplicationsByStaffIdVariables> response = await _service.executeProtected(() => _service.connector
.listCompletedApplicationsByStaffId(staffId: staffId)
.execute());
final List<Shift> 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<List<Shift>> _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<dc.GetMyApplicationsByStaffIdData, dc.GetMyApplicationsByStaffIdVariables> response =
await _service.executeProtected(() => query.execute());
final apps = response.data.applications;
final List<Shift> 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<dc.ApplicationStatus>) {
appStatus = (app.status as dc.Known<dc.ApplicationStatus>).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 @override
@@ -56,6 +195,76 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
query: query, query: query,
type: type, type: type,
); );
final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId;
if (vendorId == null || vendorId.isEmpty) {
return <Shift>[];
}
final fdc.QueryResult<dc.ListShiftRolesByVendorIdData, dc.ListShiftRolesByVendorIdVariables> 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<Shift> myShifts = await _fetchApplications();
final Set<String> myShiftIds = myShifts.map((s) => s.id).toSet();
final List<Shift> 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 @override

View File

@@ -112,9 +112,15 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
) async { ) async {
final currentState = state; final currentState = state;
if (currentState is! ShiftsLoaded) return; 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( await handleError(
emit: emit.call, emit: emit.call,
action: () async { action: () async {

View File

@@ -12,7 +12,13 @@ class LoadShiftsEvent extends ShiftsEvent {}
class LoadHistoryShiftsEvent extends ShiftsEvent {} class LoadHistoryShiftsEvent extends ShiftsEvent {}
class LoadAvailableShiftsEvent extends ShiftsEvent {} class LoadAvailableShiftsEvent extends ShiftsEvent {
final bool force;
const LoadAvailableShiftsEvent({this.force = false});
@override
List<Object?> get props => [force];
}
class LoadFindFirstEvent extends ShiftsEvent {} class LoadFindFirstEvent extends ShiftsEvent {}

View File

@@ -93,7 +93,11 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
message: state.message, message: state.message,
type: UiSnackbarType.success, type: UiSnackbarType.success,
); );
Modular.to.toShifts(selectedDate: state.shiftDate); Modular.to.toShifts(
selectedDate: state.shiftDate,
initialTab: 'find',
refreshAvailable: true,
);
} else if (state is ShiftDetailsError) { } else if (state is ShiftDetailsError) {
if (_isApplying) { if (_isApplying) {
UiSnackbar.show( UiSnackbar.show(
@@ -112,7 +116,8 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
); );
} }
Shift displayShift = widget.shift; final Shift displayShift =
state is ShiftDetailsLoaded ? state.shift : widget.shift;
final i18n = Translations.of(context).staff_shifts.shift_details; final i18n = Translations.of(context).staff_shifts.shift_details;
final duration = _calculateDuration(displayShift); final duration = _calculateDuration(displayShift);

View File

@@ -12,7 +12,13 @@ import '../widgets/tabs/history_shifts_tab.dart';
class ShiftsPage extends StatefulWidget { class ShiftsPage extends StatefulWidget {
final String? initialTab; final String? initialTab;
final DateTime? selectedDate; 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 @override
State<ShiftsPage> createState() => _ShiftsPageState(); State<ShiftsPage> createState() => _ShiftsPageState();
@@ -22,6 +28,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
late String _activeTab; late String _activeTab;
DateTime? _selectedDate; DateTime? _selectedDate;
bool _prioritizeFind = false; bool _prioritizeFind = false;
bool _refreshAvailable = false;
bool _pendingAvailableRefresh = false;
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>(); final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
@override @override
@@ -30,6 +38,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
_activeTab = widget.initialTab ?? 'myshifts'; _activeTab = widget.initialTab ?? 'myshifts';
_selectedDate = widget.selectedDate; _selectedDate = widget.selectedDate;
_prioritizeFind = widget.initialTab == 'find'; _prioritizeFind = widget.initialTab == 'find';
_refreshAvailable = widget.refreshAvailable;
_pendingAvailableRefresh = widget.refreshAvailable;
if (_prioritizeFind) { if (_prioritizeFind) {
_bloc.add(LoadFindFirstEvent()); _bloc.add(LoadFindFirstEvent());
} else { } else {
@@ -40,7 +50,9 @@ class _ShiftsPageState extends State<ShiftsPage> {
} }
if (_activeTab == 'find') { if (_activeTab == 'find') {
if (!_prioritizeFind) { if (!_prioritizeFind) {
_bloc.add(LoadAvailableShiftsEvent()); _bloc.add(
LoadAvailableShiftsEvent(force: _refreshAvailable),
);
} }
} }
// Check profile completion // Check profile completion
@@ -61,6 +73,10 @@ class _ShiftsPageState extends State<ShiftsPage> {
_selectedDate = widget.selectedDate; _selectedDate = widget.selectedDate;
}); });
} }
if (widget.refreshAvailable) {
_refreshAvailable = true;
_pendingAvailableRefresh = true;
}
} }
@override @override
@@ -79,6 +95,10 @@ class _ShiftsPageState extends State<ShiftsPage> {
} }
}, },
builder: (context, state) { builder: (context, state) {
if (_pendingAvailableRefresh && state is ShiftsLoaded) {
_pendingAvailableRefresh = false;
_bloc.add(const LoadAvailableShiftsEvent(force: true));
}
final bool baseLoaded = state is ShiftsLoaded; final bool baseLoaded = state is ShiftsLoaded;
final List<Shift> myShifts = (state is ShiftsLoaded) final List<Shift> myShifts = (state is ShiftsLoaded)
? state.myShifts ? state.myShifts

View File

@@ -77,6 +77,13 @@ class _MyShiftCardState extends State<MyShiftCard> {
String _getShiftType() { String _getShiftType() {
// Handling potential localization key availability // Handling potential localization key availability
try { 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) { if (widget.shift.durationDays != null && widget.shift.durationDays! > 30) {
return t.staff_shifts.filter.long_term; return t.staff_shifts.filter.long_term;
} }
@@ -133,6 +140,24 @@ class _MyShiftCardState extends State<MyShiftCard> {
statusText = status?.toUpperCase() ?? ""; statusText = status?.toUpperCase() ?? "";
} }
final schedules = widget.shift.schedules ?? <ShiftSchedule>[];
final hasSchedules = schedules.isNotEmpty;
final List<ShiftSchedule> 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( return GestureDetector(
onTap: () { onTap: () {
Modular.to.pushNamed( Modular.to.pushNamed(
@@ -191,8 +216,8 @@ class _MyShiftCardState extends State<MyShiftCard> {
letterSpacing: 0.5, letterSpacing: 0.5,
), ),
), ),
// Shift Type Badge // Shift Type Badge (Order type)
if (status == 'open' || status == 'pending') ...[ if ((widget.shift.orderType ?? '').isNotEmpty) ...[
const SizedBox(width: UiConstants.space2), const SizedBox(width: UiConstants.space2),
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -200,13 +225,14 @@ class _MyShiftCardState extends State<MyShiftCard> {
vertical: 2, vertical: 2,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withValues(alpha: 0.1), color: UiColors.background,
borderRadius: UiConstants.radiusSm, borderRadius: UiConstants.radiusSm,
border: Border.all(color: UiColors.border),
), ),
child: Text( child: Text(
_getShiftType(), _getShiftType(),
style: UiTypography.footnote2m.copyWith( style: UiTypography.footnote2m.copyWith(
color: UiColors.primary, color: UiColors.textSecondary,
), ),
), ),
), ),
@@ -299,7 +325,55 @@ class _MyShiftCardState extends State<MyShiftCard> {
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
// Date & Time // 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) ...[ widget.shift.durationDays! > 1) ...[
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -324,17 +398,22 @@ class _MyShiftCardState extends State<MyShiftCard> {
), ),
const SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
child: Text( child: Text(
'${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} ${_formatTime(widget.shift.endTime)}', '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)} ${_formatTime(widget.shift.endTime)}',
style: UiTypography.footnote2r.copyWith(color: UiColors.primary), style: UiTypography.footnote2r.copyWith(
color: UiColors.primary,
), ),
),
), ),
if (widget.shift.durationDays! > 1) if (widget.shift.durationDays! > 1)
Text( Text(
'... +${widget.shift.durationDays! - 1} more days', '... +${widget.shift.durationDays! - 1} more days',
style: UiTypography.footnote2r.copyWith(color: UiColors.primary.withOpacity(0.7)), style: UiTypography.footnote2r.copyWith(
) color:
UiColors.primary.withOpacity(0.7),
),
)
], ],
), ),
] else ...[ ] else ...[

View File

@@ -20,6 +20,119 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
String _searchQuery = ''; String _searchQuery = '';
String _jobType = 'all'; 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<Shift> _groupMultiDayShifts(List<Shift> shifts) {
final Map<String, List<Shift>> grouped = <String, List<Shift>>{};
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, () => <Shift>[]).add(shift);
}
final Set<String> addedGroups = <String>{};
final List<Shift> result = <Shift>[];
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<Shift> group = grouped[key] ?? <Shift>[];
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<ShiftSchedule> 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) { Widget _buildFilterTab(String id, String label) {
final isSelected = _jobType == id; final isSelected = _jobType == id;
return GestureDetector( return GestureDetector(
@@ -49,8 +162,10 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final groupedJobs = _groupMultiDayShifts(widget.availableJobs);
// Filter logic // Filter logic
final filteredJobs = widget.availableJobs.where((s) { final filteredJobs = groupedJobs.where((s) {
final matchesSearch = final matchesSearch =
s.title.toLowerCase().contains(_searchQuery.toLowerCase()) || s.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
s.location.toLowerCase().contains(_searchQuery.toLowerCase()) || s.location.toLowerCase().contains(_searchQuery.toLowerCase()) ||
@@ -60,10 +175,15 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
if (_jobType == 'all') return true; if (_jobType == 'all') return true;
if (_jobType == 'one-day') { if (_jobType == 'one-day') {
if (_isRecurring(s) || _isPermanent(s)) return false;
return s.durationDays == null || s.durationDays! <= 1; return s.durationDays == null || s.durationDays! <= 1;
} }
if (_jobType == 'multi-day') { 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; return true;
}).toList(); }).toList();

View File

@@ -66,6 +66,7 @@ class StaffShiftsModule extends Module {
return ShiftsPage( return ShiftsPage(
initialTab: queryParams['tab'] ?? args?['initialTab'], initialTab: queryParams['tab'] ?? args?['initialTab'],
selectedDate: args?['selectedDate'], selectedDate: args?['selectedDate'],
refreshAvailable: args?['refreshAvailable'] == true,
); );
}, },
); );

View File

@@ -1530,4 +1530,4 @@ packages:
version: "2.2.3" version: "2.2.3"
sdks: sdks:
dart: ">=3.10.3 <4.0.0" dart: ">=3.10.3 <4.0.0"
flutter: ">=3.38.4" flutter: ">=3.38.4 <4.0.0"

View File

@@ -3,6 +3,7 @@ publish_to: 'none'
description: "A sample project using melos and modular scaffold." description: "A sample project using melos and modular scaffold."
environment: environment:
sdk: '>=3.10.0 <4.0.0' sdk: '>=3.10.0 <4.0.0'
flutter: '>=3.38.0 <4.0.0'
workspace: workspace:
- packages/design_system - packages/design_system
- packages/core - packages/core

View File

@@ -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( query vaidateDayStaffApplication(
$staffId: UUID! $staffId: UUID!
$offset: Int $offset: Int
@@ -695,10 +784,14 @@ query listCompletedApplicationsByStaffId(
durationDays durationDays
latitude latitude
longitude longitude
orderId
order { order {
id id
eventName eventName
orderType
startDate
endDate
teamHub { teamHub {
address address

View File

@@ -254,7 +254,7 @@ query listShiftRolesByVendorId(
shiftRoles( shiftRoles(
where: { where: {
shift: { shift: {
status: {in: [IN_PROGRESS, CONFIRMED, ASSIGNED, OPEN, PENDING]} #IN_PROGRESS? PENDING? status: {in: [IN_PROGRESS, ASSIGNED, OPEN]} #IN_PROGRESS?
order: { order: {
vendorId: { eq: $vendorId } vendorId: { eq: $vendorId }
} }
@@ -511,7 +511,7 @@ query getCompletedShiftsByBusinessId(
shifts( shifts(
where: { where: {
order: { businessId: { eq: $businessId } } order: { businessId: { eq: $businessId } }
status: {in: [IN_PROGRESS, CONFIRMED, COMPLETED, OPEN]} status: {in: [IN_PROGRESS, COMPLETED, OPEN]}
date: { ge: $dateFrom, le: $dateTo } date: { ge: $dateFrom, le: $dateTo }
} }
offset: $offset offset: $offset

View File

@@ -927,7 +927,7 @@ mutation seedAll @transaction {
placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
latitude: 34.2611486 latitude: 34.2611486
longitude: -118.5010287 longitude: -118.5010287
status: ASSIGNED status: OPEN
workersNeeded: 2 workersNeeded: 2
filled: 1 filled: 1
} }
@@ -950,7 +950,7 @@ mutation seedAll @transaction {
placeId: "Eiw2ODAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" placeId: "Eiw2ODAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
latitude: 34.2611486 latitude: 34.2611486
longitude: -118.5010287 longitude: -118.5010287
status: ASSIGNED status: OPEN
workersNeeded: 2 workersNeeded: 2
filled: 1 filled: 1
} }
@@ -996,7 +996,7 @@ mutation seedAll @transaction {
placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" placeId: "Eiw0MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
latitude: 34.2611486 latitude: 34.2611486
longitude: -118.5010287 longitude: -118.5010287
status: ASSIGNED status: OPEN
workersNeeded: 2 workersNeeded: 2
filled: 1 filled: 1
} }
@@ -1042,7 +1042,7 @@ mutation seedAll @transaction {
placeId: "Eiw1MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw" placeId: "Eiw1MDAwIFNhbiBKb3NlIFN0cmVldCwgR3JhbmFkYSBIaWxscywgQ0EsIFVTQSIuKiwKFAoSCYNJZBTdmsKAEddGOfBj8LvTEhQKEglnNXI0zZrCgBEjR6om62lcVw"
latitude: 34.2611486 latitude: 34.2611486
longitude: -118.5010287 longitude: -118.5010287
status: ASSIGNED status: OPEN
workersNeeded: 2 workersNeeded: 2
filled: 1 filled: 1
} }

View File

@@ -1,9 +1,7 @@
enum ShiftStatus { enum ShiftStatus {
DRAFT DRAFT
FILLED FILLED
PENDING
ASSIGNED ASSIGNED
CONFIRMED
OPEN OPEN
IN_PROGRESS IN_PROGRESS
COMPLETED COMPLETED

View File

@@ -11,9 +11,10 @@ This document details the primary business actions and user flows within the **K
* **Description:** Secure entry into the management console. * **Description:** Secure entry into the management console.
* **Main Flow:** * **Main Flow:**
1. User enters email and password on the login screen. 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). 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 ### 2.1 Global Operational Oversight
* **Actor:** Admin * **Actor:** Admin
* **Description:** Monitoring the pulse of the entire platform. * **Description:** Monitoring the pulse of the entire platform through a customizable dashboard.
* **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.
* **Main Flow:** * **Main Flow:**
1. User navigates to Marketplace. 1. User accesses Admin Dashboard with global metrics.
2. User invites a new Vendor via email. 2. Views fill rate, total spend, performance score, and active events.
3. User sets global default rates for roles. 3. Monitors today's orders with status indicators (RAPID, Fully Staffed, Partial Staffed).
4. User audits vendor performance scores. 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 * **Actor:** Admin
* **Description:** Configuring platform-wide settings and security. * **Description:** Managing the vendor network and partnerships.
* **Main Flow:** User updates system configurations -> Reviews security audit logs -> Manages internal support tickets. * **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. Client Executive Workflows
### 3.1 Strategic Insights (Savings Engine) ### 3.1 Dashboard Overview
* **Actor:** Client Executive * **Actor:** Client Executive
* **Description:** Using AI to optimize labor spend. * **Description:** Personalized dashboard for order and labor management.
* **Main Flow:** * **Main Flow:**
1. User opens the Savings Engine. 1. User opens Client Dashboard with customizable widgets.
2. User reviews identified cost-saving opportunities. 2. Views action items (overdue invoices, unfilled orders, rapid requests).
3. User clicks "Approve Strategy" to implement recommendations (e.g., vendor consolidation). 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 ### 3.2 Order Management
* **Actor:** Client Executive / Finance Admin * **Actor:** Client Executive / Operations Manager
* **Description:** Managing corporate financial obligations. * **Description:** Creating and managing staffing requests.
* **Main Flow:** User views all pending invoices -> Downloads detailed line-item reports -> Processes payments to Krow. * **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 * **Actor:** Client Executive
* **Description:** High-level monitoring of venue operations. * **Description:** Finding and managing vendor relationships.
* **Main Flow:** User views a summary of their venue orders -> Reviews ratings of assigned staff -> Monitors fulfillment rates. * **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. 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 * **Actor:** Vendor Manager
* **Description:** Fulfilling client staffing requests. * **Description:** Fulfilling client staffing requests.
* **Main Flow:** * **Main Flow:**
1. User views incoming shift requests. 1. User views incoming orders via "Orders" section.
2. User selects a shift. 2. Filters orders by tab (All, Conflicts, Upcoming, Active, Past).
3. User uses the **Worker Selection Tool** to assign the best-fit staff. 3. Reviews order details (business, hub, event, date/time, roles).
4. User confirms assignment. 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 * **Actor:** Vendor Manager
* **Description:** Maintaining their agency's supply of workers. * **Description:** Managing agency's worker pool.
* **Main Flow:** User navigates to Roster -> Adds new workers -> Updates compliance documents and certifications -> Edits worker profiles. * **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 * **Actor:** Vendor Manager
* **Description:** Managing agency revenue and worker payouts. * **Description:** Streamlined multi-step onboarding for new workers.
* **Main Flow:** User views payout history -> Submits invoices for completed shifts -> Tracks pending payments from Krow. * **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 ### 5.1 Order Details & History
* **Actor:** All Roles * **Actor:** All Roles
* **Description:** Accessing granular data for any specific staffing request. * **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 ### 5.2 Invoice Detail View
* **Actor:** Admin, Client, Vendor * **Actor:** Admin, Client, Vendor
* **Description:** Reviewing the breakdown of costs for a billing period. * **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 # Use Case Diagram
```mermaid ```mermaid
flowchart TD flowchart TD
subgraph AccessControl [Access & Authentication] subgraph AccessControl [Access & Authentication]
@@ -118,53 +474,166 @@ flowchart TD
subgraph AdminWorkflows [Admin Workflows] subgraph AdminWorkflows [Admin Workflows]
AdminDash --> GlobalOversight[Global Oversight] AdminDash --> GlobalOversight[Global Oversight]
GlobalOversight --> EcosystemWheel[Ecosystem Wheel]
GlobalOversight --> ViewAllOrders[View All Orders] GlobalOversight --> ViewAllOrders[View All Orders]
GlobalOversight --> ViewAllUsers[View All Users] GlobalOversight --> ActionItems[Action Items]
AdminDash --> MarketplaceMgmt[Marketplace Management] AdminDash --> VendorMgmt[Vendor Management]
MarketplaceMgmt --> OnboardVendor[Onboard Vendor] VendorMgmt --> ApproveVendors[Approve Vendors]
MarketplaceMgmt --> ManageRates[Manage Global Rates] VendorMgmt --> SetTiers[Set Vendor Tiers]
AdminDash --> SystemAdmin[System Administration] AdminDash --> WorkforceMgmt[Workforce Management]
SystemAdmin --> ConfigSettings[Configure Settings] WorkforceMgmt --> StaffDirectory[Staff Directory]
SystemAdmin --> AuditLogs[View Audit Logs] WorkforceMgmt --> Compliance[Compliance Dashboard]
AdminDash --> AnalyticsReports[Analytics & Reports]
AnalyticsReports --> ReportsDashboard[Reports Dashboard]
AnalyticsReports --> ActivityLog[Activity Log]
end end
subgraph ClientWorkflows [Client Executive Workflows] subgraph ClientWorkflows [Client Executive Workflows]
ClientDash --> ClientInsights[Strategic Insights] ClientDash --> ClientActionItems[Action Items]
ClientInsights --> SavingsEngine[Savings Engine] ClientActionItems --> ReviewAlerts[Review Alerts]
SavingsEngine --> ViewOpp[View Opportunity]
ViewOpp --> ApproveStrategy[Approve Strategy]
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 --> ViewInvoices[View Invoices]
ClientFinance --> PayInvoice[Pay Invoice] ViewInvoices --> InvoiceDetail[Invoice Detail]
InvoiceDetail --> PayInvoice[Pay Invoice]
InvoiceDetail --> DisputeInvoice[Dispute Invoice]
ClientDash --> ClientOps[Operations Overview] ClientDash --> Communication[Communication]
ClientOps --> ViewMyOrders[View My Orders] Communication --> MessageCenter[Message Center]
ClientOps --> ViewMyStaff[View Assigned Staff] Communication --> SupportCenter[Support Center]
end end
subgraph VendorWorkflows [Vendor Workflows] subgraph VendorWorkflows [Vendor Workflows]
VendorDash --> VendorOps[Vendor Operations] VendorDash --> VendorKPIs[KPI Dashboard]
VendorOps --> ViewRequests[View Shift Requests] VendorKPIs --> RevenueStats[Revenue Stats]
ViewRequests --> AssignWorker[Assign Worker] VendorKPIs --> TopClients[Top Clients]
VendorOps --> ManageRoster[Manage Worker Roster] VendorKPIs --> TopPerformers[Top Performers]
ManageRoster --> UpdateWorkerProfile[Update Worker Profile]
VendorDash --> VendorFinance[Vendor Finance] VendorDash --> OrderFulfillment[Order Fulfillment]
VendorFinance --> ViewPayouts[View Payouts] 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 --> SubmitInvoice[Submit Invoice]
VendorFinance --> TrackPayments[Track Payments]
VendorDash --> VendorPerformance[Performance Analytics]
VendorPerformance --> FillRate[Fill Rate]
VendorPerformance --> CSAT[Client Satisfaction]
VendorPerformance --> RevenueAnalysis[Revenue Analysis]
end end
subgraph SharedModules [Shared Functional Modules] subgraph SharedModules [Shared Functional Modules]
ViewAllOrders -.-> OrderDetail[Order Details] TaskBoard[Task Board] -.-> Tasks[Manage Tasks]
ViewMyOrders -.-> OrderDetail Tasks -.-> DragDrop[Drag & Drop Status]
ViewRequests -.-> OrderDetail
AssignWorker -.-> WorkerSelection[Worker Selection Tool] MessageCenter -.-> Conversations[Conversations]
Conversations -.-> SendMessage[Send Message]
ViewInvoices -.-> InvoiceDetail[Invoice Detail View] ReportsDashboard -.-> ReportTypes[Report Types]
SubmitInvoice -.-> InvoiceDetail 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 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.