@burdenoff/microfe-movethewheels 2026.1101.3 → 2026.1102.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/components/CustomerDeliveryUpdatesFields.js +57 -0
  2. package/dist/components/CustomerNotificationSettingsCard.js +368 -0
  3. package/dist/components/UpcomingDemandPanel.js +75 -77
  4. package/dist/generated/tracking-quality-operations.d.ts +275 -0
  5. package/dist/generated/wspace-operations.d.ts +3 -0
  6. package/dist/hooks/useCustomers.js +9 -3
  7. package/dist/hooks/useDemandForecast.js +1 -1
  8. package/dist/movethewheels/components/CustomerDeliveryUpdatesFields.d.ts +12 -0
  9. package/dist/movethewheels/components/CustomerNotificationSettingsCard.d.ts +3 -0
  10. package/dist/movethewheels/hooks/useDemandForecast.d.ts +3 -0
  11. package/dist/movethewheels/types/index.d.ts +4 -0
  12. package/dist/movethewheels/utils/customerNotifications.d.ts +66 -0
  13. package/dist/node_modules/lucide-react/dist/esm/icons/bell-ring.js +22 -0
  14. package/dist/pages/CustomerDetailsPage.js +279 -231
  15. package/dist/pages/SettingsPage.js +242 -238
  16. package/dist/utils/customerNotifications.js +83 -0
  17. package/package.json +3 -2
  18. package/src/generated/tracking-quality-operations.ts +304 -0
  19. package/src/generated/wspace-operations.ts +6 -6
  20. package/src/movethewheels/components/CustomerDeliveryUpdatesFields.tsx +67 -0
  21. package/src/movethewheels/components/CustomerNotificationSettingsCard.tsx +465 -0
  22. package/src/movethewheels/components/UpcomingDemandPanel.tsx +71 -29
  23. package/src/movethewheels/hooks/useCustomers.ts +17 -0
  24. package/src/movethewheels/hooks/useDemandForecast.ts +7 -0
  25. package/src/movethewheels/pages/CustomerDetailsPage.tsx +39 -0
  26. package/src/movethewheels/pages/SettingsPage.tsx +221 -208
  27. package/src/movethewheels/types/index.ts +4 -0
  28. package/src/movethewheels/utils/customerNotifications.ts +209 -0
  29. package/src/operations/wspace/movethewheels/queries/forecast.graphql +3 -0
@@ -148,6 +148,8 @@ export type CreateCustomerInput = {
148
148
  context?: InputMaybe<ContextInput>;
149
149
  email?: InputMaybe<Scalars['String']['input']>;
150
150
  name: Scalars['String']['input'];
151
+ notifyByEmail?: InputMaybe<Scalars['Boolean']['input']>;
152
+ notifyBySms?: InputMaybe<Scalars['Boolean']['input']>;
151
153
  phone?: InputMaybe<Scalars['String']['input']>;
152
154
  rating?: InputMaybe<Scalars['Float']['input']>;
153
155
  taxId?: InputMaybe<Scalars['String']['input']>;
@@ -939,6 +941,10 @@ export type MtwCustomer = {
939
941
  email?: Maybe<Scalars['String']['output']>;
940
942
  id: Scalars['ID']['output'];
941
943
  name: Scalars['String']['output'];
944
+ /** Send this customer delivery notifications by email (BOFF-4402). The workspace must also switch customer notifications on. */
945
+ notifyByEmail: Scalars['Boolean']['output'];
946
+ /** Send this customer delivery notifications by SMS. Needs a phone number with its country code. */
947
+ notifyBySms: Scalars['Boolean']['output'];
942
948
  onBehalfOf: Scalars['String']['output'];
943
949
  phone?: Maybe<Scalars['String']['output']>;
944
950
  rating?: Maybe<Scalars['Float']['output']>;
@@ -948,6 +954,91 @@ export type MtwCustomer = {
948
954
  updatedBy?: Maybe<Scalars['String']['output']>;
949
955
  workspaceId: Scalars['ID']['output'];
950
956
  };
957
+ /** One notification sent (or attempted) to a shipment's customer. */
958
+ export type MtwCustomerNotification = {
959
+ __typename?: 'MtwCustomerNotification';
960
+ attempts: Scalars['Int']['output'];
961
+ channel: MtwCustomerNotificationChannel;
962
+ createdAt: Scalars['DateTime']['output'];
963
+ customerId?: Maybe<Scalars['ID']['output']>;
964
+ event: MtwCustomerNotificationEvent;
965
+ id: Scalars['ID']['output'];
966
+ lastError?: Maybe<Scalars['String']['output']>;
967
+ /** Masked address, e.g. j***@example.com. */
968
+ recipient?: Maybe<Scalars['String']['output']>;
969
+ sentAt?: Maybe<Scalars['DateTime']['output']>;
970
+ shipmentId: Scalars['ID']['output'];
971
+ shipmentNumber?: Maybe<Scalars['String']['output']>;
972
+ status: MtwCustomerNotificationStatus;
973
+ subject?: Maybe<Scalars['String']['output']>;
974
+ updatedAt: Scalars['DateTime']['output'];
975
+ };
976
+ /** How a customer is reached. */
977
+ export type MtwCustomerNotificationChannel = 'EMAIL' | 'SMS';
978
+ /** A shipment lifecycle moment a customer can be told about (BOFF-4402). */
979
+ export type MtwCustomerNotificationEvent =
980
+ /** The shipment was created for the customer. */
981
+ 'BOOKED'
982
+ /** The risk monitor expects the shipment to miss its promised delivery. Sent once per promise. */
983
+ | 'DELAYED'
984
+ /** The shipment was delivered. */
985
+ | 'DELIVERED'
986
+ /** Delivery could not be completed. */
987
+ | 'FAILED'
988
+ /** The latest tracking event says the shipment is out for delivery. */
989
+ | 'OUT_FOR_DELIVERY'
990
+ /** The shipment was collected. */
991
+ | 'PICKED_UP';
992
+ export type MtwCustomerNotificationFilterInput = {
993
+ event?: InputMaybe<MtwCustomerNotificationEvent>;
994
+ shipmentId?: InputMaybe<Scalars['ID']['input']>;
995
+ status?: InputMaybe<MtwCustomerNotificationStatus>;
996
+ };
997
+ /** A page of customer notifications, newest first. */
998
+ export type MtwCustomerNotificationPage = {
999
+ __typename?: 'MtwCustomerNotificationPage';
1000
+ hasMore: Scalars['Boolean']['output'];
1001
+ items: Array<MtwCustomerNotification>;
1002
+ total: Scalars['Int']['output'];
1003
+ };
1004
+ /** What a customer notification for an event looks like, rendered with sample data. */
1005
+ export type MtwCustomerNotificationPreview = {
1006
+ __typename?: 'MtwCustomerNotificationPreview';
1007
+ emailText: Scalars['String']['output'];
1008
+ event: MtwCustomerNotificationEvent;
1009
+ smsText: Scalars['String']['output'];
1010
+ subject: Scalars['String']['output'];
1011
+ };
1012
+ /**
1013
+ * Whether and how this workspace tells its customers about their shipments.
1014
+ * Off until a workspace admin switches it on; only shipment changes made after
1015
+ * that moment notify, so switching on never mails customers about old shipments.
1016
+ */
1017
+ export type MtwCustomerNotificationSettings = {
1018
+ __typename?: 'MtwCustomerNotificationSettings';
1019
+ emailEnabled: Scalars['Boolean']['output'];
1020
+ enabled: Scalars['Boolean']['output'];
1021
+ /** When notifications were last switched on. */
1022
+ enabledAt?: Maybe<Scalars['DateTime']['output']>;
1023
+ /** Events that notify customers. */
1024
+ events: Array<MtwCustomerNotificationEvent>;
1025
+ /** False until the workspace saves these settings. */
1026
+ isCustomized: Scalars['Boolean']['output'];
1027
+ smsEnabled: Scalars['Boolean']['output'];
1028
+ updatedAt?: Maybe<Scalars['DateTime']['output']>;
1029
+ updatedBy?: Maybe<Scalars['ID']['output']>;
1030
+ workspaceId: Scalars['ID']['output'];
1031
+ };
1032
+ /** Where a customer notification stands. */
1033
+ export type MtwCustomerNotificationStatus =
1034
+ /** Not accepted; retried by a later evaluation up to three times. */
1035
+ 'FAILED'
1036
+ /** Claimed and being sent. */
1037
+ | 'PENDING'
1038
+ /** Accepted by the platform notification service. */
1039
+ | 'SENT'
1040
+ /** Not sent because the customer has no usable address for the channel. */
1041
+ | 'SKIPPED';
951
1042
  export type MtwCustomerStats = {
952
1043
  __typename?: 'MtwCustomerStats';
953
1044
  /** Unavailable because customers have no lifecycle status field. */
@@ -2960,6 +3051,9 @@ export type MtwRiskForecast = {
2960
3051
  evaluatedAt: Scalars['DateTime']['output'];
2961
3052
  /** Forecasting method, e.g. DETERMINISTIC_V1. */
2962
3053
  method: Scalars['String']['output'];
3054
+ /** AI narration of this forecast. Null when AI is disabled, credits are spent, or none was generated yet. */
3055
+ narration?: Maybe<MtwRiskNarration>;
3056
+ narrationStatus: MtwRiskNarrationStatus;
2963
3057
  predictedDeliveryEarliest?: Maybe<Scalars['DateTime']['output']>;
2964
3058
  predictedDeliveryLatest?: Maybe<Scalars['DateTime']['output']>;
2965
3059
  predictedSlipMinutes: Scalars['Int']['output'];
@@ -2975,6 +3069,40 @@ export type MtwRiskForecast = {
2975
3069
  status: ShipmentStatus;
2976
3070
  };
2977
3071
  export type MtwRiskLevel = 'HIGH' | 'LOW' | 'MEDIUM' | 'NONE';
3072
+ /**
3073
+ * Plain-language explanation of a risk forecast, written by a model from the
3074
+ * forecast's computed features. It never changes the forecast's numbers.
3075
+ */
3076
+ export type MtwRiskNarration = {
3077
+ __typename?: 'MtwRiskNarration';
3078
+ /** Suggested message to the end customer re-promising delivery, when one is warranted. */
3079
+ customerMessage?: Maybe<Scalars['String']['output']>;
3080
+ /** Why the shipment is predicted to slip, in two or three sentences. */
3081
+ explanation: Scalars['String']['output'];
3082
+ generatedAt: Scalars['DateTime']['output'];
3083
+ id: Scalars['ID']['output'];
3084
+ /** Language the narration was written in. */
3085
+ locale: Scalars['String']['output'];
3086
+ /** Provider-qualified model that wrote it, e.g. openai/gpt-4o-mini. */
3087
+ model: Scalars['String']['output'];
3088
+ /** interactive, scheduled or event. */
3089
+ trigger: Scalars['String']['output'];
3090
+ };
3091
+ export type MtwRiskNarrationStatus =
3092
+ /** AI narration is switched off for this environment. */
3093
+ 'AI_DISABLED'
3094
+ /** A narration for the forecast as it stands now is attached. */
3095
+ | 'AVAILABLE'
3096
+ /** The workspace has spent its MoveTheWheels AI credits; no model call was made. */
3097
+ | 'CREDITS_EXHAUSTED'
3098
+ /** The workspace reached its daily narration cap; no model call was made. */
3099
+ | 'DAILY_CAP_REACHED'
3100
+ /** The forecast has no risk signals, so there is nothing to explain. */
3101
+ | 'NOT_NEEDED'
3102
+ /** No narration has been generated for this forecast yet; call mtwNarrateShipmentRiskForecast. */
3103
+ | 'NOT_REQUESTED'
3104
+ /** The model could not produce a narration this time; the forecast is unchanged. */
3105
+ | 'UNAVAILABLE';
2978
3106
  export type MtwRiskRecommendedAction = 'MONITOR' | 'NONE' | 'REASSIGN_VEHICLE' | 'REPROMISE_DELIVERY' | 'REQUEST_TRACKING_UPDATE';
2979
3107
  /**
2980
3108
  * One piece of evidence behind a risk forecast. `weight` is the number of risk
@@ -2989,6 +3117,49 @@ export type MtwRiskSignal = {
2989
3117
  weight: Scalars['Int']['output'];
2990
3118
  };
2991
3119
  export type MtwRiskSignalCode = 'CARRIER_LATE_HISTORY' | 'EXCESS_DWELL' | 'LEG_SLIP' | 'OPEN_LEG_OVERDUE' | 'ROUTE_DURATION_OVERRUN' | 'TRACKING_STALE' | 'VEHICLE_UNAVAILABLE';
3120
+ export type MtwRiskSweep = {
3121
+ __typename?: 'MtwRiskSweep';
3122
+ atRisk: Scalars['Int']['output'];
3123
+ /**
3124
+ * True when this call was a redelivery of an earlier scheduler fire with the
3125
+ * same idempotency key; the original sweep is returned and nothing ran again.
3126
+ */
3127
+ duplicate: Scalars['Boolean']['output'];
3128
+ durationMs?: Maybe<Scalars['Int']['output']>;
3129
+ episodesClosed: Scalars['Int']['output'];
3130
+ episodesOpened: Scalars['Int']['output'];
3131
+ error?: Maybe<Scalars['String']['output']>;
3132
+ finishedAt?: Maybe<Scalars['DateTime']['output']>;
3133
+ id: Scalars['ID']['output'];
3134
+ notificationsSent: Scalars['Int']['output'];
3135
+ scheduledFor?: Maybe<Scalars['DateTime']['output']>;
3136
+ shipmentsEvaluated: Scalars['Int']['output'];
3137
+ /** KILL_SWITCH when the monitor is switched off. */
3138
+ skippedReason?: Maybe<Scalars['String']['output']>;
3139
+ startedAt: Scalars['DateTime']['output'];
3140
+ /** RUNNING, COMPLETED, SKIPPED or FAILED. */
3141
+ status: Scalars['String']['output'];
3142
+ /** SCHEDULED (fired by wspace-scheduler-svc) or MANUAL. */
3143
+ trigger: Scalars['String']['output'];
3144
+ workspaceId: Scalars['ID']['output'];
3145
+ };
3146
+ /**
3147
+ * The workspace's cadence job in wspace-scheduler-svc. A workspace admin can pause
3148
+ * it on the shared Schedules screen; the monitor leaves a paused job alone.
3149
+ */
3150
+ export type MtwRiskSweepSchedule = {
3151
+ __typename?: 'MtwRiskSweepSchedule';
3152
+ cronExpression: Scalars['String']['output'];
3153
+ id: Scalars['ID']['output'];
3154
+ lastError?: Maybe<Scalars['String']['output']>;
3155
+ lastSweepAt?: Maybe<Scalars['DateTime']['output']>;
3156
+ schedulerJobId?: Maybe<Scalars['ID']['output']>;
3157
+ /** PENDING, ACTIVE or FAILED. */
3158
+ status: Scalars['String']['output'];
3159
+ timezone: Scalars['String']['output'];
3160
+ updatedAt: Scalars['DateTime']['output'];
3161
+ workspaceId: Scalars['ID']['output'];
3162
+ };
2992
3163
  export type MtwRoute = {
2993
3164
  __typename?: 'MtwRoute';
2994
3165
  actualDistance?: Maybe<Scalars['Float']['output']>;
@@ -3025,6 +3196,15 @@ export type MtwRouteStats = {
3025
3196
  totalDistance: Scalars['Float']['output'];
3026
3197
  totalDuration: Scalars['Float']['output'];
3027
3198
  };
3199
+ /**
3200
+ * Set by wspace-scheduler-svc at fire time: idempotencyKey is
3201
+ * "<scheduleId>:<scheduledTime>" and triggerData carries scheduledTime, firedAt,
3202
+ * timezone, scheduleId and eventId. Ignored for on-demand runs by a person.
3203
+ */
3204
+ export type MtwRunRiskSweepInput = {
3205
+ idempotencyKey?: InputMaybe<Scalars['String']['input']>;
3206
+ triggerData?: InputMaybe<Scalars['JSON']['input']>;
3207
+ };
3028
3208
  /** Whether the provider currently has capacity for an offering. */
3029
3209
  export type MtwServiceAvailability = 'AVAILABLE' | 'LIMITED' | 'UNAVAILABLE';
3030
3210
  /** A discoverable service a provider offers, with indicative price and availability. */
@@ -3209,6 +3389,39 @@ export type MtwShipmentLeg = {
3209
3389
  widthCm?: Maybe<Scalars['Float']['output']>;
3210
3390
  workspaceId: Scalars['ID']['output'];
3211
3391
  };
3392
+ /**
3393
+ * The current (or last) risk episode of one shipment. An episode opens when the
3394
+ * forecast first reports the shipment at risk, notifies once, and closes when the
3395
+ * forecast recovers, the promise passes or the shipment finishes.
3396
+ */
3397
+ export type MtwShipmentRiskEpisode = {
3398
+ __typename?: 'MtwShipmentRiskEpisode';
3399
+ /** RECOVERED, BREACHED, COMPLETED or REMOVED. */
3400
+ closeReason?: Maybe<Scalars['String']['output']>;
3401
+ closedAt?: Maybe<Scalars['DateTime']['output']>;
3402
+ /** How many episodes this shipment has had. */
3403
+ episodeCount: Scalars['Int']['output'];
3404
+ id: Scalars['ID']['output'];
3405
+ lastEvaluatedAt: Scalars['DateTime']['output'];
3406
+ lastNotificationError?: Maybe<Scalars['String']['output']>;
3407
+ /** EVENT or SWEEP: what evaluated the shipment last. */
3408
+ lastTrigger: Scalars['String']['output'];
3409
+ notificationAttempts: Scalars['Int']['output'];
3410
+ notificationsSent: Scalars['Int']['output'];
3411
+ /** When the episode's notification reached at least one recipient. */
3412
+ notifiedAt?: Maybe<Scalars['DateTime']['output']>;
3413
+ openedAt: Scalars['DateTime']['output'];
3414
+ predictedSlipMinutes: Scalars['Int']['output'];
3415
+ /** The promised delivery the episode was raised against. */
3416
+ promisedDelivery?: Maybe<Scalars['DateTime']['output']>;
3417
+ recommendedAction?: Maybe<Scalars['String']['output']>;
3418
+ riskLevel: Scalars['String']['output'];
3419
+ riskScore: Scalars['Int']['output'];
3420
+ shipmentId: Scalars['ID']['output'];
3421
+ signals: Array<Scalars['String']['output']>;
3422
+ /** OPEN or CLOSED. */
3423
+ state: Scalars['String']['output'];
3424
+ };
3212
3425
  export type MtwShipmentStatusDecision = {
3213
3426
  __typename?: 'MtwShipmentStatusDecision';
3214
3427
  authorityEpoch: Scalars['Int']['output'];
@@ -3502,6 +3715,8 @@ export type MtwTrackingAlert = {
3502
3715
  /** Stable alert id, `<shipmentId>:<alertType>`. */
3503
3716
  id: Scalars['ID']['output'];
3504
3717
  message: Scalars['String']['output'];
3718
+ /** AI narration of the forecast behind an AT_RISK alert, when one was generated for its current signals. */
3719
+ narration?: Maybe<MtwRiskNarration>;
3505
3720
  predictedDelivery?: Maybe<Scalars['DateTime']['output']>;
3506
3721
  /** The shipment's promised delivery (estimatedDelivery) the alert was raised against. */
3507
3722
  promisedDelivery?: Maybe<Scalars['DateTime']['output']>;
@@ -3842,6 +4057,24 @@ export type Mutation = {
3842
4057
  importMtwDrivers: MtwImportResult;
3843
4058
  importMtwVehicles: MtwImportResult;
3844
4059
  importMtwWarehouses: MtwImportResult;
4060
+ /**
4061
+ * Register, verify or repair the workspace's risk sweep with the platform
4062
+ * scheduler. Idempotent; leaves a job an operator paused alone.
4063
+ */
4064
+ mtwEnsureRiskSweepSchedule?: Maybe<MtwRiskSweepSchedule>;
4065
+ /**
4066
+ * Generate (or reuse) the AI narration for a shipment's current risk forecast
4067
+ * and return the forecast with it attached. With AI disabled, credits spent or
4068
+ * the daily cap reached, this returns the deterministic forecast unchanged and
4069
+ * makes no model call; `narrationStatus` says why.
4070
+ */
4071
+ mtwNarrateShipmentRiskForecast: MtwRiskForecast;
4072
+ /**
4073
+ * Re-forecast every in-flight shipment of the workspace and notify for any that
4074
+ * started slipping. Fired by wspace-scheduler-svc as this service's own service
4075
+ * actor; a workspace user may also run it on demand.
4076
+ */
4077
+ mtwRunRiskSweep: MtwRiskSweep;
3845
4078
  openMtwContainerCycle: MtwContainerCycle;
3846
4079
  openMtwFreeTimeClock: MtwFreeTimeClock;
3847
4080
  optimizeRoute: MtwRoute;
@@ -3887,6 +4120,8 @@ export type Mutation = {
3887
4120
  updateDriverStatus: MtwDriver;
3888
4121
  updateIntegration: MtwIntegration;
3889
4122
  updateInventoryItem: MtwInventoryItem;
4123
+ /** Switch customer notifications on or off and choose channels and events. */
4124
+ updateMtwCustomerNotificationSettings: MtwCustomerNotificationSettings;
3890
4125
  /** Save the workspace's MoveTheWheels operational defaults. Every field is validated. */
3891
4126
  updateMtwModuleSettings: MtwModuleSettings;
3892
4127
  updateRoute: MtwRoute;
@@ -4043,6 +4278,12 @@ export type MutationImportMtwVehiclesArgs = {
4043
4278
  export type MutationImportMtwWarehousesArgs = {
4044
4279
  input: MtwImportInput;
4045
4280
  };
4281
+ export type MutationMtwNarrateShipmentRiskForecastArgs = {
4282
+ shipmentId: Scalars['ID']['input'];
4283
+ };
4284
+ export type MutationMtwRunRiskSweepArgs = {
4285
+ input?: InputMaybe<MtwRunRiskSweepInput>;
4286
+ };
4046
4287
  export type MutationOpenMtwContainerCycleArgs = {
4047
4288
  input: OpenMtwContainerCycleInput;
4048
4289
  };
@@ -4188,6 +4429,9 @@ export type MutationUpdateInventoryItemArgs = {
4188
4429
  id: Scalars['ID']['input'];
4189
4430
  input: UpdateInventoryItemInput;
4190
4431
  };
4432
+ export type MutationUpdateMtwCustomerNotificationSettingsArgs = {
4433
+ input: UpdateMtwCustomerNotificationSettingsInput;
4434
+ };
4191
4435
  export type MutationUpdateMtwModuleSettingsArgs = {
4192
4436
  input: UpdateMtwModuleSettingsInput;
4193
4437
  };
@@ -4372,6 +4616,12 @@ export type Query = {
4372
4616
  mtwContainerReleaseDecisionPage: MtwContainerReleaseDecisionPage;
4373
4617
  mtwContainerRepairPage: MtwContainerRepairPage;
4374
4618
  mtwContainers: Array<MtwContainer>;
4619
+ /** Sample messages for each event, signed with this workspace's name. */
4620
+ mtwCustomerNotificationPreviews: Array<MtwCustomerNotificationPreview>;
4621
+ /** This workspace's customer notification settings; off when none were saved. */
4622
+ mtwCustomerNotificationSettings: MtwCustomerNotificationSettings;
4623
+ /** Customer notifications sent or attempted in this workspace, newest first. */
4624
+ mtwCustomerNotifications: MtwCustomerNotificationPage;
4375
4625
  /** Exact immutable instruction, including a proposal's historical owner; not a current-eligibility claim. */
4376
4626
  mtwEmptyReturnInstruction: MtwEmptyReturnInstruction;
4377
4627
  mtwEmptyReturnInstructionPage: MtwEmptyReturnInstructionPage;
@@ -4411,6 +4661,12 @@ export type Query = {
4411
4661
  mtwReuseProposalPage: MtwReuseProposalPage;
4412
4662
  mtwReuseReservationPage: MtwReuseReservationPage;
4413
4663
  mtwReuseReservationTransitionPage: MtwReuseReservationTransitionPage;
4664
+ /** The workspace's risk-sweep registration with the platform scheduler, if any. */
4665
+ mtwRiskSweepSchedule?: Maybe<MtwRiskSweepSchedule>;
4666
+ /** Recent risk sweeps of the workspace, newest first. */
4667
+ mtwRiskSweeps: Array<MtwRiskSweep>;
4668
+ /** The shipment's current or last risk episode, or null if it has never been at risk. */
4669
+ mtwShipmentRiskEpisode?: Maybe<MtwShipmentRiskEpisode>;
4414
4670
  mtwShipmentRiskForecast: MtwRiskForecast;
4415
4671
  mtwStockBindingAssessment: MtwStockBindingAssessment;
4416
4672
  mtwStockBindingAssessmentPage: MtwStockBindingAssessmentPage;
@@ -4639,6 +4895,10 @@ export type QueryMtwContainersArgs = {
4639
4895
  pagination?: InputMaybe<MtwContainerPagination>;
4640
4896
  search?: InputMaybe<Scalars['String']['input']>;
4641
4897
  };
4898
+ export type QueryMtwCustomerNotificationsArgs = {
4899
+ filter?: InputMaybe<MtwCustomerNotificationFilterInput>;
4900
+ pagination?: InputMaybe<PaginationInput>;
4901
+ };
4642
4902
  export type QueryMtwEmptyReturnInstructionArgs = {
4643
4903
  id: Scalars['ID']['input'];
4644
4904
  };
@@ -4750,6 +5010,12 @@ export type QueryMtwReuseReservationTransitionPageArgs = {
4750
5010
  pagination?: InputMaybe<MtwContainerPagination>;
4751
5011
  reservationId: Scalars['ID']['input'];
4752
5012
  };
5013
+ export type QueryMtwRiskSweepsArgs = {
5014
+ pagination?: InputMaybe<PaginationInput>;
5015
+ };
5016
+ export type QueryMtwShipmentRiskEpisodeArgs = {
5017
+ shipmentId: Scalars['ID']['input'];
5018
+ };
4753
5019
  export type QueryMtwShipmentRiskForecastArgs = {
4754
5020
  shipmentId: Scalars['ID']['input'];
4755
5021
  };
@@ -5260,6 +5526,8 @@ export type UpdateCustomerInput = {
5260
5526
  context?: InputMaybe<ContextInput>;
5261
5527
  email?: InputMaybe<Scalars['String']['input']>;
5262
5528
  name?: InputMaybe<Scalars['String']['input']>;
5529
+ notifyByEmail?: InputMaybe<Scalars['Boolean']['input']>;
5530
+ notifyBySms?: InputMaybe<Scalars['Boolean']['input']>;
5263
5531
  phone?: InputMaybe<Scalars['String']['input']>;
5264
5532
  rating?: InputMaybe<Scalars['Float']['input']>;
5265
5533
  taxId?: InputMaybe<Scalars['String']['input']>;
@@ -5332,6 +5600,13 @@ export type UpdateInventoryItemInput = {
5332
5600
  safetyStock?: InputMaybe<Scalars['Int']['input']>;
5333
5601
  unitCost?: InputMaybe<Scalars['Float']['input']>;
5334
5602
  };
5603
+ /** Changes to the customer notification settings. Omitted fields keep their value. */
5604
+ export type UpdateMtwCustomerNotificationSettingsInput = {
5605
+ emailEnabled?: InputMaybe<Scalars['Boolean']['input']>;
5606
+ enabled?: InputMaybe<Scalars['Boolean']['input']>;
5607
+ events?: InputMaybe<Array<MtwCustomerNotificationEvent>>;
5608
+ smsEnabled?: InputMaybe<Scalars['Boolean']['input']>;
5609
+ };
5335
5610
  /**
5336
5611
  * Changes to the workspace's MoveTheWheels defaults. Omitted fields keep their
5337
5612
  * current value. `timezone: null` (or an empty string) clears the time zone so
@@ -1141,6 +1141,7 @@ export type GetMovethewheelsDemandForecastByLaneQuery = {
1141
1141
  demandForecastByLane: Array<{
1142
1142
  scopeType: Types.DemandForecastScope;
1143
1143
  scopeKey: string;
1144
+ scopeLabel: string;
1144
1145
  periodStart: string;
1145
1146
  periodEnd: string;
1146
1147
  predictedVolume: number;
@@ -1157,6 +1158,7 @@ export type GetMovethewheelsDemandForecastByCustomerQuery = {
1157
1158
  demandForecastByCustomer: Array<{
1158
1159
  scopeType: Types.DemandForecastScope;
1159
1160
  scopeKey: string;
1161
+ scopeLabel: string;
1160
1162
  periodStart: string;
1161
1163
  periodEnd: string;
1162
1164
  predictedVolume: number;
@@ -1174,6 +1176,7 @@ export type GetMovethewheelsDemandForecastVarianceQuery = {
1174
1176
  demandForecastVariance: Array<{
1175
1177
  scopeType: Types.DemandForecastScope;
1176
1178
  scopeKey: string;
1179
+ scopeLabel: string;
1177
1180
  periodStart: string;
1178
1181
  periodEnd: string;
1179
1182
  predictedVolume: number;
@@ -2,7 +2,7 @@ import { useMoveTheWheels as e } from "../providers/MoveTheWheelsProvider.js";
2
2
  import { executeGraphQL as t } from "../utils/graphqlClient.js";
3
3
  import { useCallback as n, useEffect as r, useMemo as i, useState as a } from "react";
4
4
  //#region src/movethewheels/hooks/useCustomers.ts
5
- var o = "\n query ListCustomers($filter: CustomerFilterInput, $pagination: PaginationInput) {\n customers(filter: $filter, pagination: $pagination) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n createdAt\n updatedAt\n deletedAt\n }\n customersCount(filter: $filter)\n }\n", s = "\n query GetCustomer($id: ID!) {\n customer(id: $id) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n createdAt\n updatedAt\n deletedAt\n }\n }\n", c = "\n mutation CreateCustomer($input: CreateCustomerInput!) {\n createCustomer(input: $input) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n createdAt\n updatedAt\n deletedAt\n }\n }\n", l = "\n mutation UpdateCustomer($id: ID!, $input: UpdateCustomerInput!) {\n updateCustomer(id: $id, input: $input) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n createdAt\n updatedAt\n deletedAt\n }\n }\n", u = "\n mutation DeleteCustomer($id: ID!) {\n deleteCustomer(id: $id)\n }\n";
5
+ var o = "\n query ListCustomers($filter: CustomerFilterInput, $pagination: PaginationInput) {\n customers(filter: $filter, pagination: $pagination) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n notifyByEmail\n notifyBySms\n createdAt\n updatedAt\n deletedAt\n }\n customersCount(filter: $filter)\n }\n", s = "\n query GetCustomer($id: ID!) {\n customer(id: $id) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n notifyByEmail\n notifyBySms\n createdAt\n updatedAt\n deletedAt\n }\n }\n", c = "\n mutation CreateCustomer($input: CreateCustomerInput!) {\n createCustomer(input: $input) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n notifyByEmail\n notifyBySms\n createdAt\n updatedAt\n deletedAt\n }\n }\n", l = "\n mutation UpdateCustomer($id: ID!, $input: UpdateCustomerInput!) {\n updateCustomer(id: $id, input: $input) {\n id\n name\n email\n phone\n type\n company\n address\n businessLicense\n taxId\n rating\n notifyByEmail\n notifyBySms\n createdAt\n updatedAt\n deletedAt\n }\n }\n", u = "\n mutation DeleteCustomer($id: ID!) {\n deleteCustomer(id: $id)\n }\n";
6
6
  function d(e) {
7
7
  let t = e.address ?? {};
8
8
  return {
@@ -16,6 +16,8 @@ function d(e) {
16
16
  businessLicense: e.businessLicense ?? void 0,
17
17
  taxId: e.taxId ?? void 0,
18
18
  rating: e.rating ?? void 0,
19
+ notifyByEmail: e.notifyByEmail ?? !0,
20
+ notifyBySms: e.notifyBySms ?? !1,
19
21
  createdAt: new Date(e.createdAt),
20
22
  updatedAt: new Date(e.updatedAt)
21
23
  };
@@ -120,7 +122,9 @@ function f(s = {}) {
120
122
  address: e.address,
121
123
  businessLicense: e.businessLicense,
122
124
  taxId: e.taxId,
123
- rating: e.rating
125
+ rating: e.rating,
126
+ notifyByEmail: e.notifyByEmail,
127
+ notifyBySms: e.notifyBySms
124
128
  } })).createCustomer);
125
129
  return C((e) => [n, ...e]), n;
126
130
  }, [P]),
@@ -136,7 +140,9 @@ function f(s = {}) {
136
140
  address: n.address,
137
141
  businessLicense: n.businessLicense,
138
142
  taxId: n.taxId,
139
- rating: n.rating
143
+ rating: n.rating,
144
+ notifyByEmail: n.notifyByEmail,
145
+ notifyBySms: n.notifyBySms
140
146
  }
141
147
  })).updateCustomer);
142
148
  return C((t) => t.map((t) => t.id === e ? r : t)), r;
@@ -2,7 +2,7 @@ import { useMoveTheWheels as e } from "../providers/MoveTheWheelsProvider.js";
2
2
  import { executeGraphQL as t } from "../utils/graphqlClient.js";
3
3
  import { useCallback as n, useEffect as r, useMemo as i, useState as a } from "react";
4
4
  //#region src/movethewheels/hooks/useDemandForecast.ts
5
- var o = "\n query GetMovethewheelsDemandForecast($weeks: Int!, $varianceLimit: Int!) {\n demandForecastByLane(weeks: $weeks) {\n scopeType\n scopeKey\n periodStart\n periodEnd\n predictedVolume\n predictedRevenue\n actualVolume\n actualRevenue\n confidence\n }\n demandForecastByCustomer(weeks: $weeks) {\n scopeType\n scopeKey\n periodStart\n periodEnd\n predictedVolume\n predictedRevenue\n actualVolume\n actualRevenue\n confidence\n }\n laneVariance: demandForecastVariance(scopeType: LANE, limit: $varianceLimit) {\n scopeType\n scopeKey\n periodStart\n periodEnd\n predictedVolume\n actualVolume\n volumeVariance\n predictedRevenue\n actualRevenue\n revenueVariance\n }\n customerVariance: demandForecastVariance(scopeType: CUSTOMER, limit: $varianceLimit) {\n scopeType\n scopeKey\n periodStart\n periodEnd\n predictedVolume\n actualVolume\n volumeVariance\n predictedRevenue\n actualRevenue\n revenueVariance\n }\n }\n";
5
+ var o = "\n query GetMovethewheelsDemandForecast($weeks: Int!, $varianceLimit: Int!) {\n demandForecastByLane(weeks: $weeks) {\n scopeType\n scopeKey\n scopeLabel\n periodStart\n periodEnd\n predictedVolume\n predictedRevenue\n actualVolume\n actualRevenue\n confidence\n }\n demandForecastByCustomer(weeks: $weeks) {\n scopeType\n scopeKey\n scopeLabel\n periodStart\n periodEnd\n predictedVolume\n predictedRevenue\n actualVolume\n actualRevenue\n confidence\n }\n laneVariance: demandForecastVariance(scopeType: LANE, limit: $varianceLimit) {\n scopeType\n scopeKey\n scopeLabel\n periodStart\n periodEnd\n predictedVolume\n actualVolume\n volumeVariance\n predictedRevenue\n actualRevenue\n revenueVariance\n }\n customerVariance: demandForecastVariance(scopeType: CUSTOMER, limit: $varianceLimit) {\n scopeType\n scopeKey\n scopeLabel\n periodStart\n periodEnd\n predictedVolume\n actualVolume\n volumeVariance\n predictedRevenue\n actualRevenue\n revenueVariance\n }\n }\n";
6
6
  function s(s = 4, c = 8) {
7
7
  let { apiGatewayUrl: l, authToken: u, workspaceToken: d, workspaceId: f, tenantId: p, currentUser: m } = e(), [h, g] = a([]), [_, v] = a([]), [y, b] = a([]), [x, S] = a([]), [C, w] = a(!1), [T, E] = a(null), D = i(() => ({
8
8
  apiGatewayUrl: l,
@@ -0,0 +1,12 @@
1
+ export interface DeliveryUpdatesValue {
2
+ notifyByEmail: boolean;
3
+ notifyBySms: boolean;
4
+ }
5
+ export declare function CustomerDeliveryUpdatesFields({ idPrefix, value, onChange, disabled, }: {
6
+ idPrefix: string;
7
+ value: DeliveryUpdatesValue;
8
+ onChange: (next: DeliveryUpdatesValue) => void;
9
+ disabled?: boolean;
10
+ }): import('react').JSX.Element;
11
+ /** "Email and SMS", "Email", "SMS" or "Off". */
12
+ export declare function deliveryUpdatesSummary(value: DeliveryUpdatesValue): string;
@@ -0,0 +1,3 @@
1
+ export declare function CustomerNotificationSettingsCard({ readOnly }: {
2
+ readOnly: boolean;
3
+ }): import('react').JSX.Element;
@@ -6,6 +6,8 @@
6
6
  export interface ForecastPoint {
7
7
  scopeType: 'LANE' | 'CUSTOMER';
8
8
  scopeKey: string;
9
+ /** Customer name or `Origin → Destination`; falls back to the key. */
10
+ scopeLabel: string;
9
11
  periodStart: string;
10
12
  periodEnd: string;
11
13
  predictedVolume: number;
@@ -17,6 +19,7 @@ export interface ForecastPoint {
17
19
  export interface ForecastVariance {
18
20
  scopeType: 'LANE' | 'CUSTOMER';
19
21
  scopeKey: string;
22
+ scopeLabel: string;
20
23
  periodStart: string;
21
24
  periodEnd: string;
22
25
  predictedVolume: number;
@@ -141,6 +141,10 @@ export interface Customer {
141
141
  businessLicense?: string;
142
142
  taxId?: string;
143
143
  rating?: number;
144
+ /** Delivery notifications by email (BOFF-4402). */
145
+ notifyByEmail: boolean;
146
+ /** Delivery notifications by SMS; needs a phone number with its country code. */
147
+ notifyBySms: boolean;
144
148
  createdAt: Date;
145
149
  updatedAt: Date;
146
150
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Customer delivery notifications — client side of wspace-movethewheels-svc's
3
+ * customer notification settings, history and previews (BOFF-4402).
4
+ *
5
+ * The service decides what to send and when; this module holds the GraphQL
6
+ * documents, the labels and the pure draft helpers the Settings page uses.
7
+ * Kept free of React so the contract can be unit-tested.
8
+ */
9
+ export type MtwCustomerNotificationEvent = 'BOOKED' | 'PICKED_UP' | 'OUT_FOR_DELIVERY' | 'DELIVERED' | 'DELAYED' | 'FAILED';
10
+ export type MtwCustomerNotificationChannel = 'EMAIL' | 'SMS';
11
+ export type MtwCustomerNotificationStatus = 'PENDING' | 'SENT' | 'FAILED' | 'SKIPPED';
12
+ export interface MtwCustomerNotificationSettings {
13
+ workspaceId: string;
14
+ enabled: boolean;
15
+ enabledAt: string | null;
16
+ emailEnabled: boolean;
17
+ smsEnabled: boolean;
18
+ events: MtwCustomerNotificationEvent[];
19
+ isCustomized: boolean;
20
+ updatedAt: string | null;
21
+ updatedBy: string | null;
22
+ }
23
+ export type MtwCustomerNotificationSettingsInput = Partial<Pick<MtwCustomerNotificationSettings, 'enabled' | 'emailEnabled' | 'smsEnabled' | 'events'>>;
24
+ export interface MtwCustomerNotification {
25
+ id: string;
26
+ shipmentId: string;
27
+ shipmentNumber: string | null;
28
+ customerId: string | null;
29
+ event: MtwCustomerNotificationEvent;
30
+ channel: MtwCustomerNotificationChannel;
31
+ status: MtwCustomerNotificationStatus;
32
+ recipient: string | null;
33
+ subject: string | null;
34
+ attempts: number;
35
+ lastError: string | null;
36
+ sentAt: string | null;
37
+ createdAt: string;
38
+ }
39
+ export interface MtwCustomerNotificationPreview {
40
+ event: MtwCustomerNotificationEvent;
41
+ subject: string;
42
+ emailText: string;
43
+ smsText: string;
44
+ }
45
+ /** Every event, in the order a shipment reaches them. */
46
+ export declare const CUSTOMER_NOTIFICATION_EVENTS: ReadonlyArray<{
47
+ value: MtwCustomerNotificationEvent;
48
+ label: string;
49
+ description: string;
50
+ }>;
51
+ export declare function eventLabel(event: MtwCustomerNotificationEvent): string;
52
+ export declare const STATUS_LABELS: Record<MtwCustomerNotificationStatus, string>;
53
+ /** Off, email only, every event — what a workspace sees until it saves. */
54
+ export declare function defaultNotificationSettings(workspaceId?: string): MtwCustomerNotificationSettings;
55
+ /** Events in canonical order without duplicates. */
56
+ export declare function orderedEvents(events: readonly MtwCustomerNotificationEvent[]): MtwCustomerNotificationEvent[];
57
+ /** Add or remove one event from a selection. */
58
+ export declare function toggleEvent(events: readonly MtwCustomerNotificationEvent[], event: MtwCustomerNotificationEvent, on: boolean): MtwCustomerNotificationEvent[];
59
+ /** Only the fields the draft changed, ready for the update mutation. */
60
+ export declare function changedNotificationSettings(saved: MtwCustomerNotificationSettings, draft: Pick<MtwCustomerNotificationSettings, 'enabled' | 'emailEnabled' | 'smsEnabled' | 'events'>): MtwCustomerNotificationSettingsInput;
61
+ /** Why the draft cannot be saved, or null. Switching on needs a channel and an event. */
62
+ export declare function notificationDraftError(draft: Pick<MtwCustomerNotificationSettings, 'enabled' | 'emailEnabled' | 'smsEnabled' | 'events'>): string | null;
63
+ export declare const MTW_CUSTOMER_NOTIFICATION_SETTINGS_QUERY = "\n query MtwCustomerNotificationSettings {\n mtwCustomerNotificationSettings {\n workspaceId\n enabled\n enabledAt\n emailEnabled\n smsEnabled\n events\n isCustomized\n updatedAt\n updatedBy\n}\n }\n";
64
+ export declare const UPDATE_MTW_CUSTOMER_NOTIFICATION_SETTINGS_MUTATION = "\n mutation UpdateMtwCustomerNotificationSettings($input: UpdateMtwCustomerNotificationSettingsInput!) {\n updateMtwCustomerNotificationSettings(input: $input) {\n workspaceId\n enabled\n enabledAt\n emailEnabled\n smsEnabled\n events\n isCustomized\n updatedAt\n updatedBy\n}\n }\n";
65
+ export declare const MTW_CUSTOMER_NOTIFICATIONS_QUERY = "\n query MtwCustomerNotifications(\n $filter: MtwCustomerNotificationFilterInput\n $pagination: PaginationInput\n ) {\n mtwCustomerNotifications(filter: $filter, pagination: $pagination) {\n total\n hasMore\n items {\n id\n shipmentId\n shipmentNumber\n customerId\n event\n channel\n status\n recipient\n subject\n attempts\n lastError\n sentAt\n createdAt\n }\n }\n }\n";
66
+ export declare const MTW_CUSTOMER_NOTIFICATION_PREVIEWS_QUERY = "\n query MtwCustomerNotificationPreviews {\n mtwCustomerNotificationPreviews {\n event\n subject\n emailText\n smsText\n }\n }\n";
@@ -0,0 +1,22 @@
1
+ import { createLucideIcon as e } from "../createLucideIcon.js";
2
+ //#region node_modules/lucide-react/dist/esm/icons/bell-ring.js
3
+ var t = e("BellRing", [
4
+ ["path", {
5
+ d: "M10.268 21a2 2 0 0 0 3.464 0",
6
+ key: "vwvbt9"
7
+ }],
8
+ ["path", {
9
+ d: "M22 8c0-2.3-.8-4.3-2-6",
10
+ key: "5bb3ad"
11
+ }],
12
+ ["path", {
13
+ d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326",
14
+ key: "11g9vi"
15
+ }],
16
+ ["path", {
17
+ d: "M4 2C2.8 3.7 2 5.7 2 8",
18
+ key: "tap9e0"
19
+ }]
20
+ ]);
21
+ //#endregion
22
+ export { t as BellRing };