@burdenoff/microfe-movethewheels 2026.804.1 → 2026.822.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 (28) hide show
  1. package/dist/components/CarbonFootprintPanel.js +108 -0
  2. package/dist/components/UpcomingDemandPanel.js +184 -0
  3. package/dist/components/index.js +1 -0
  4. package/dist/generated/wspace-operations.d.ts +147 -0
  5. package/dist/generated/wspace-types.d.ts +63 -0
  6. package/dist/hooks/useAnalytics.js +45 -4
  7. package/dist/hooks/useDemandForecast.js +54 -0
  8. package/dist/index.js +29 -28
  9. package/dist/movethewheels/components/CarbonFootprintPanel.d.ts +7 -0
  10. package/dist/movethewheels/components/UpcomingDemandPanel.d.ts +13 -0
  11. package/dist/movethewheels/components/index.d.ts +1 -0
  12. package/dist/movethewheels/hooks/index.d.ts +1 -1
  13. package/dist/movethewheels/hooks/useAnalytics.d.ts +22 -0
  14. package/dist/movethewheels/hooks/useDemandForecast.d.ts +39 -0
  15. package/dist/node_modules/lucide-react/dist/esm/icons/leaf.js +11 -0
  16. package/dist/node_modules/lucide-react/dist/esm/icons/trending-down.js +11 -0
  17. package/dist/pages/AnalyticsPage.js +82 -69
  18. package/package.json +2 -2
  19. package/src/generated/wspace-operations.ts +177 -0
  20. package/src/generated/wspace-types.ts +74 -0
  21. package/src/movethewheels/components/CarbonFootprintPanel.tsx +133 -0
  22. package/src/movethewheels/components/UpcomingDemandPanel.tsx +237 -0
  23. package/src/movethewheels/components/index.ts +3 -0
  24. package/src/movethewheels/hooks/index.ts +2 -0
  25. package/src/movethewheels/hooks/useAnalytics.ts +86 -0
  26. package/src/movethewheels/hooks/useDemandForecast.ts +161 -0
  27. package/src/movethewheels/pages/AnalyticsPage.tsx +27 -0
  28. package/src/operations/wspace/movethewheels/queries/forecast.graphql +47 -0
@@ -462,6 +462,28 @@ export type GetWarehouseQueryVariables = Types.Exact<{
462
462
 
463
463
  export type GetWarehouseQuery = { __typename?: 'Query', warehouse?: { __typename?: 'Warehouse', id: string, name: string, code: string, type: Types.WarehouseType, address: Record<string, unknown>, contactInfo?: Record<string, unknown> | null, operatingHours?: Record<string, unknown> | null, capabilities?: Record<string, unknown> | null, status: string, totalCapacity?: number | null, usedCapacity?: number | null, createdAt: string, updatedAt: string, locations: Array<{ __typename?: 'WarehouseLocation', id: string, locationCode: string, zone?: string | null, aisle?: string | null, rack?: string | null, shelf?: string | null, bin?: string | null, locationType?: string | null, capacity?: number | null, status: string }> } | null };
464
464
 
465
+ export type GetMovethewheelsDemandForecastByLaneQueryVariables = Types.Exact<{
466
+ weeks: Types.Scalars['Int']['input'];
467
+ }>;
468
+
469
+
470
+ export type GetMovethewheelsDemandForecastByLaneQuery = { __typename?: 'Query', demandForecastByLane: Array<{ __typename?: 'MtwDemandForecastPoint', scopeType: Types.DemandForecastScope, scopeKey: string, periodStart: string, periodEnd: string, predictedVolume: number, predictedRevenue: number, actualVolume?: number | null, actualRevenue?: number | null, confidence: number }> };
471
+
472
+ export type GetMovethewheelsDemandForecastByCustomerQueryVariables = Types.Exact<{
473
+ weeks: Types.Scalars['Int']['input'];
474
+ }>;
475
+
476
+
477
+ export type GetMovethewheelsDemandForecastByCustomerQuery = { __typename?: 'Query', demandForecastByCustomer: Array<{ __typename?: 'MtwDemandForecastPoint', scopeType: Types.DemandForecastScope, scopeKey: string, periodStart: string, periodEnd: string, predictedVolume: number, predictedRevenue: number, actualVolume?: number | null, actualRevenue?: number | null, confidence: number }> };
478
+
479
+ export type GetMovethewheelsDemandForecastVarianceQueryVariables = Types.Exact<{
480
+ scopeType: Types.DemandForecastScope;
481
+ limit: Types.Scalars['Int']['input'];
482
+ }>;
483
+
484
+
485
+ export type GetMovethewheelsDemandForecastVarianceQuery = { __typename?: 'Query', demandForecastVariance: Array<{ __typename?: 'MtwDemandForecastVariance', scopeType: Types.DemandForecastScope, scopeKey: string, periodStart: string, periodEnd: string, predictedVolume: number, actualVolume: number, volumeVariance: number, predictedRevenue: number, actualRevenue: number, revenueVariance: number }> };
486
+
465
487
  export type ListInventoryItemsQueryVariables = Types.Exact<{
466
488
  filter?: Types.InputMaybe<Types.InventoryFilterInput>;
467
489
  pagination?: Types.InputMaybe<Types.PaginationInput>;
@@ -2918,6 +2940,161 @@ export type GetWarehouseQueryHookResult = ReturnType<typeof useGetWarehouseQuery
2918
2940
  export type GetWarehouseLazyQueryHookResult = ReturnType<typeof useGetWarehouseLazyQuery>;
2919
2941
  export type GetWarehouseSuspenseQueryHookResult = ReturnType<typeof useGetWarehouseSuspenseQuery>;
2920
2942
  export type GetWarehouseQueryResult = Apollo.QueryResult<Types.GetWarehouseQuery, Types.GetWarehouseQueryVariables>;
2943
+ export const GetMovethewheelsDemandForecastByLaneDocument = gql`
2944
+ query GetMovethewheelsDemandForecastByLane($weeks: Int!) {
2945
+ demandForecastByLane(weeks: $weeks) {
2946
+ scopeType
2947
+ scopeKey
2948
+ periodStart
2949
+ periodEnd
2950
+ predictedVolume
2951
+ predictedRevenue
2952
+ actualVolume
2953
+ actualRevenue
2954
+ confidence
2955
+ }
2956
+ }
2957
+ `;
2958
+
2959
+ /**
2960
+ * __useGetMovethewheelsDemandForecastByLaneQuery__
2961
+ *
2962
+ * To run a query within a React component, call `useGetMovethewheelsDemandForecastByLaneQuery` and pass it any options that fit your needs.
2963
+ * When your component renders, `useGetMovethewheelsDemandForecastByLaneQuery` returns an object from Apollo Client that contains loading, error, and data properties
2964
+ * you can use to render your UI.
2965
+ *
2966
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
2967
+ *
2968
+ * @example
2969
+ * const { data, loading, error } = useGetMovethewheelsDemandForecastByLaneQuery({
2970
+ * variables: {
2971
+ * weeks: // value for 'weeks'
2972
+ * },
2973
+ * });
2974
+ */
2975
+ export function useGetMovethewheelsDemandForecastByLaneQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables> & ({ variables: Types.GetMovethewheelsDemandForecastByLaneQueryVariables; skip?: boolean; } | { skip: boolean; }) ) {
2976
+ const options = {...defaultOptions, ...baseOptions}
2977
+ return ApolloReactHooks.useQuery<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>(GetMovethewheelsDemandForecastByLaneDocument, options);
2978
+ }
2979
+ export function useGetMovethewheelsDemandForecastByLaneLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>) {
2980
+ const options = {...defaultOptions, ...baseOptions}
2981
+ return ApolloReactHooks.useLazyQuery<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>(GetMovethewheelsDemandForecastByLaneDocument, options);
2982
+ }
2983
+ // @ts-ignore
2984
+ export function useGetMovethewheelsDemandForecastByLaneSuspenseQuery(baseOptions?: ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>;
2985
+ export function useGetMovethewheelsDemandForecastByLaneSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetMovethewheelsDemandForecastByLaneQuery | undefined, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>;
2986
+ export function useGetMovethewheelsDemandForecastByLaneSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>) {
2987
+ const options = baseOptions === ApolloReactHooks.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
2988
+ return ApolloReactHooks.useSuspenseQuery<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>(GetMovethewheelsDemandForecastByLaneDocument, options);
2989
+ }
2990
+ export type GetMovethewheelsDemandForecastByLaneQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastByLaneQuery>;
2991
+ export type GetMovethewheelsDemandForecastByLaneLazyQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastByLaneLazyQuery>;
2992
+ export type GetMovethewheelsDemandForecastByLaneSuspenseQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastByLaneSuspenseQuery>;
2993
+ export type GetMovethewheelsDemandForecastByLaneQueryResult = Apollo.QueryResult<Types.GetMovethewheelsDemandForecastByLaneQuery, Types.GetMovethewheelsDemandForecastByLaneQueryVariables>;
2994
+ export const GetMovethewheelsDemandForecastByCustomerDocument = gql`
2995
+ query GetMovethewheelsDemandForecastByCustomer($weeks: Int!) {
2996
+ demandForecastByCustomer(weeks: $weeks) {
2997
+ scopeType
2998
+ scopeKey
2999
+ periodStart
3000
+ periodEnd
3001
+ predictedVolume
3002
+ predictedRevenue
3003
+ actualVolume
3004
+ actualRevenue
3005
+ confidence
3006
+ }
3007
+ }
3008
+ `;
3009
+
3010
+ /**
3011
+ * __useGetMovethewheelsDemandForecastByCustomerQuery__
3012
+ *
3013
+ * To run a query within a React component, call `useGetMovethewheelsDemandForecastByCustomerQuery` and pass it any options that fit your needs.
3014
+ * When your component renders, `useGetMovethewheelsDemandForecastByCustomerQuery` returns an object from Apollo Client that contains loading, error, and data properties
3015
+ * you can use to render your UI.
3016
+ *
3017
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
3018
+ *
3019
+ * @example
3020
+ * const { data, loading, error } = useGetMovethewheelsDemandForecastByCustomerQuery({
3021
+ * variables: {
3022
+ * weeks: // value for 'weeks'
3023
+ * },
3024
+ * });
3025
+ */
3026
+ export function useGetMovethewheelsDemandForecastByCustomerQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables> & ({ variables: Types.GetMovethewheelsDemandForecastByCustomerQueryVariables; skip?: boolean; } | { skip: boolean; }) ) {
3027
+ const options = {...defaultOptions, ...baseOptions}
3028
+ return ApolloReactHooks.useQuery<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>(GetMovethewheelsDemandForecastByCustomerDocument, options);
3029
+ }
3030
+ export function useGetMovethewheelsDemandForecastByCustomerLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>) {
3031
+ const options = {...defaultOptions, ...baseOptions}
3032
+ return ApolloReactHooks.useLazyQuery<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>(GetMovethewheelsDemandForecastByCustomerDocument, options);
3033
+ }
3034
+ // @ts-ignore
3035
+ export function useGetMovethewheelsDemandForecastByCustomerSuspenseQuery(baseOptions?: ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>;
3036
+ export function useGetMovethewheelsDemandForecastByCustomerSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetMovethewheelsDemandForecastByCustomerQuery | undefined, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>;
3037
+ export function useGetMovethewheelsDemandForecastByCustomerSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>) {
3038
+ const options = baseOptions === ApolloReactHooks.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
3039
+ return ApolloReactHooks.useSuspenseQuery<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>(GetMovethewheelsDemandForecastByCustomerDocument, options);
3040
+ }
3041
+ export type GetMovethewheelsDemandForecastByCustomerQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastByCustomerQuery>;
3042
+ export type GetMovethewheelsDemandForecastByCustomerLazyQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastByCustomerLazyQuery>;
3043
+ export type GetMovethewheelsDemandForecastByCustomerSuspenseQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastByCustomerSuspenseQuery>;
3044
+ export type GetMovethewheelsDemandForecastByCustomerQueryResult = Apollo.QueryResult<Types.GetMovethewheelsDemandForecastByCustomerQuery, Types.GetMovethewheelsDemandForecastByCustomerQueryVariables>;
3045
+ export const GetMovethewheelsDemandForecastVarianceDocument = gql`
3046
+ query GetMovethewheelsDemandForecastVariance($scopeType: DemandForecastScope!, $limit: Int!) {
3047
+ demandForecastVariance(scopeType: $scopeType, limit: $limit) {
3048
+ scopeType
3049
+ scopeKey
3050
+ periodStart
3051
+ periodEnd
3052
+ predictedVolume
3053
+ actualVolume
3054
+ volumeVariance
3055
+ predictedRevenue
3056
+ actualRevenue
3057
+ revenueVariance
3058
+ }
3059
+ }
3060
+ `;
3061
+
3062
+ /**
3063
+ * __useGetMovethewheelsDemandForecastVarianceQuery__
3064
+ *
3065
+ * To run a query within a React component, call `useGetMovethewheelsDemandForecastVarianceQuery` and pass it any options that fit your needs.
3066
+ * When your component renders, `useGetMovethewheelsDemandForecastVarianceQuery` returns an object from Apollo Client that contains loading, error, and data properties
3067
+ * you can use to render your UI.
3068
+ *
3069
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
3070
+ *
3071
+ * @example
3072
+ * const { data, loading, error } = useGetMovethewheelsDemandForecastVarianceQuery({
3073
+ * variables: {
3074
+ * scopeType: // value for 'scopeType'
3075
+ * limit: // value for 'limit'
3076
+ * },
3077
+ * });
3078
+ */
3079
+ export function useGetMovethewheelsDemandForecastVarianceQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables> & ({ variables: Types.GetMovethewheelsDemandForecastVarianceQueryVariables; skip?: boolean; } | { skip: boolean; }) ) {
3080
+ const options = {...defaultOptions, ...baseOptions}
3081
+ return ApolloReactHooks.useQuery<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>(GetMovethewheelsDemandForecastVarianceDocument, options);
3082
+ }
3083
+ export function useGetMovethewheelsDemandForecastVarianceLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>) {
3084
+ const options = {...defaultOptions, ...baseOptions}
3085
+ return ApolloReactHooks.useLazyQuery<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>(GetMovethewheelsDemandForecastVarianceDocument, options);
3086
+ }
3087
+ // @ts-ignore
3088
+ export function useGetMovethewheelsDemandForecastVarianceSuspenseQuery(baseOptions?: ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>;
3089
+ export function useGetMovethewheelsDemandForecastVarianceSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetMovethewheelsDemandForecastVarianceQuery | undefined, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>;
3090
+ export function useGetMovethewheelsDemandForecastVarianceSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>) {
3091
+ const options = baseOptions === ApolloReactHooks.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
3092
+ return ApolloReactHooks.useSuspenseQuery<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>(GetMovethewheelsDemandForecastVarianceDocument, options);
3093
+ }
3094
+ export type GetMovethewheelsDemandForecastVarianceQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastVarianceQuery>;
3095
+ export type GetMovethewheelsDemandForecastVarianceLazyQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastVarianceLazyQuery>;
3096
+ export type GetMovethewheelsDemandForecastVarianceSuspenseQueryHookResult = ReturnType<typeof useGetMovethewheelsDemandForecastVarianceSuspenseQuery>;
3097
+ export type GetMovethewheelsDemandForecastVarianceQueryResult = Apollo.QueryResult<Types.GetMovethewheelsDemandForecastVarianceQuery, Types.GetMovethewheelsDemandForecastVarianceQueryVariables>;
2921
3098
  export const ListInventoryItemsDocument = gql`
2922
3099
  query ListInventoryItems($filter: InventoryFilterInput, $pagination: PaginationInput) {
2923
3100
  inventoryItems(filter: $filter, pagination: $pagination) {
@@ -9831,6 +9831,11 @@ export enum DeliveryChannelType {
9831
9831
  Webhook = 'WEBHOOK'
9832
9832
  }
9833
9833
 
9834
+ export enum DemandForecastScope {
9835
+ Customer = 'CUSTOMER',
9836
+ Lane = 'LANE'
9837
+ }
9838
+
9834
9839
  export type DependencyHealth = {
9835
9840
  __typename?: 'DependencyHealth';
9836
9841
  message: Scalars['String']['output'];
@@ -20581,6 +20586,33 @@ export type MovethewheelsReportFilterInput = {
20581
20586
  type?: InputMaybe<Scalars['String']['input']>;
20582
20587
  };
20583
20588
 
20589
+ export type MtwDemandForecastPoint = {
20590
+ __typename?: 'MtwDemandForecastPoint';
20591
+ actualRevenue?: Maybe<Scalars['Float']['output']>;
20592
+ actualVolume?: Maybe<Scalars['Int']['output']>;
20593
+ confidence: Scalars['Float']['output'];
20594
+ periodEnd: Scalars['DateTime']['output'];
20595
+ periodStart: Scalars['DateTime']['output'];
20596
+ predictedRevenue: Scalars['Float']['output'];
20597
+ predictedVolume: Scalars['Int']['output'];
20598
+ scopeKey: Scalars['String']['output'];
20599
+ scopeType: DemandForecastScope;
20600
+ };
20601
+
20602
+ export type MtwDemandForecastVariance = {
20603
+ __typename?: 'MtwDemandForecastVariance';
20604
+ actualRevenue: Scalars['Float']['output'];
20605
+ actualVolume: Scalars['Int']['output'];
20606
+ periodEnd: Scalars['DateTime']['output'];
20607
+ periodStart: Scalars['DateTime']['output'];
20608
+ predictedRevenue: Scalars['Float']['output'];
20609
+ predictedVolume: Scalars['Int']['output'];
20610
+ revenueVariance: Scalars['Float']['output'];
20611
+ scopeKey: Scalars['String']['output'];
20612
+ scopeType: DemandForecastScope;
20613
+ volumeVariance: Scalars['Int']['output'];
20614
+ };
20615
+
20584
20616
  /**
20585
20617
  * Base Mutation type with placeholder field.
20586
20618
  * Extended by individual mutation files.
@@ -22335,6 +22367,7 @@ export type Mutation = {
22335
22367
  /** Reactivate an archived workspace (sets status to ACTIVE) */
22336
22368
  reactivateWorkspace: Workspace;
22337
22369
  rebuildKnowledgeBaseIndex: Scalars['Boolean']['output'];
22370
+ recalculateDemandForecasts: Scalars['Int']['output'];
22338
22371
  /**
22339
22372
  * Recalculate resource usage for the current workspace.
22340
22373
  * Workspace ID is read from gateway context (x-workspace-id header).
@@ -32325,6 +32358,15 @@ export type MutationRebuildKnowledgeBaseIndexArgs = {
32325
32358
  };
32326
32359
 
32327
32360
 
32361
+ /**
32362
+ * Base Mutation type with placeholder field.
32363
+ * Extended by individual mutation files.
32364
+ */
32365
+ export type MutationRecalculateDemandForecastsArgs = {
32366
+ weeksOfHistory?: InputMaybe<Scalars['Int']['input']>;
32367
+ };
32368
+
32369
+
32328
32370
  /**
32329
32371
  * Base Mutation type with placeholder field.
32330
32372
  * Extended by individual mutation files.
@@ -41214,6 +41256,10 @@ export type Query = {
41214
41256
  deadLetterQueueEntry?: Maybe<DeadLetterQueue>;
41215
41257
  decryptSecret?: Maybe<DecryptedSecret>;
41216
41258
  defaultVibeDeck?: Maybe<VibeDeck>;
41259
+ /** Demand forecasting / S&OP v1 (BOFF-4393). Heuristic next-week forecasts. */
41260
+ demandForecastByCustomer: Array<MtwDemandForecastPoint>;
41261
+ demandForecastByLane: Array<MtwDemandForecastPoint>;
41262
+ demandForecastVariance: Array<MtwDemandForecastVariance>;
41217
41263
  /** Check the health status of all system dependencies */
41218
41264
  dependenciesHealth: HealthCheckResponse;
41219
41265
  /**
@@ -43672,6 +43718,34 @@ export type QueryDecryptSecretArgs = {
43672
43718
  };
43673
43719
 
43674
43720
 
43721
+ /**
43722
+ * Base Query type with placeholder field.
43723
+ * Extended by individual query files.
43724
+ */
43725
+ export type QueryDemandForecastByCustomerArgs = {
43726
+ weeks?: InputMaybe<Scalars['Int']['input']>;
43727
+ };
43728
+
43729
+
43730
+ /**
43731
+ * Base Query type with placeholder field.
43732
+ * Extended by individual query files.
43733
+ */
43734
+ export type QueryDemandForecastByLaneArgs = {
43735
+ weeks?: InputMaybe<Scalars['Int']['input']>;
43736
+ };
43737
+
43738
+
43739
+ /**
43740
+ * Base Query type with placeholder field.
43741
+ * Extended by individual query files.
43742
+ */
43743
+ export type QueryDemandForecastVarianceArgs = {
43744
+ limit?: InputMaybe<Scalars['Int']['input']>;
43745
+ scopeType: DemandForecastScope;
43746
+ };
43747
+
43748
+
43675
43749
  /**
43676
43750
  * Base Query type with placeholder field.
43677
43751
  * Extended by individual query files.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Carbon footprint panel (BOFF-4391).
3
+ *
4
+ * Surfaces workspace-level CO2e totals and per-vehicle intensity. Data comes
5
+ * from Query.emissionsSummary via useEmissionsSummary.
6
+ */
7
+
8
+ import { Leaf, Truck } from 'lucide-react';
9
+ import { Card, CardContent, CardHeader, CardTitle } from './ui';
10
+ import { MetricCard, MetricGrid } from './MetricCard';
11
+ import { useEmissionsSummary } from '../hooks/useAnalytics';
12
+
13
+ function formatKg(value: number): string {
14
+ if (value >= 1000) {
15
+ return `${(value / 1000).toFixed(2)} t`;
16
+ }
17
+ return `${value.toFixed(1)} kg`;
18
+ }
19
+
20
+ function formatKm(value: number): string {
21
+ if (value >= 1000) {
22
+ return `${(value / 1000).toFixed(1)}k km`;
23
+ }
24
+ return `${value.toFixed(1)} km`;
25
+ }
26
+
27
+ export function CarbonFootprintPanel() {
28
+ const { summary, isLoading, error } = useEmissionsSummary();
29
+
30
+ if (error) {
31
+ return (
32
+ <Card className="border-status-error-border">
33
+ <CardContent className="p-6 text-center">
34
+ <p className="text-sm text-status-error-text">Couldn&apos;t load emissions data</p>
35
+ </CardContent>
36
+ </Card>
37
+ );
38
+ }
39
+
40
+ if (isLoading) {
41
+ return (
42
+ <Card>
43
+ <CardHeader>
44
+ <CardTitle className="flex items-center gap-2">
45
+ <Leaf size={18} className="text-status-success-text" />
46
+ Carbon footprint
47
+ </CardTitle>
48
+ </CardHeader>
49
+ <CardContent>
50
+ <MetricGrid columns={3}>
51
+ {Array.from({ length: 3 }).map((_, i) => (
52
+ <div key={i} className="h-24 rounded-xl bg-bg-sunken motion-safe:animate-pulse" />
53
+ ))}
54
+ </MetricGrid>
55
+ </CardContent>
56
+ </Card>
57
+ );
58
+ }
59
+
60
+ const hasData = summary.totalDistanceKm > 0;
61
+
62
+ return (
63
+ <Card>
64
+ <CardHeader>
65
+ <CardTitle className="flex items-center gap-2">
66
+ <Leaf size={18} className="text-status-success-text" />
67
+ Carbon footprint
68
+ </CardTitle>
69
+ </CardHeader>
70
+ <CardContent className="space-y-6">
71
+ <MetricGrid columns={3}>
72
+ <MetricCard
73
+ label="Total CO2e"
74
+ value={formatKg(summary.totalCo2eKg)}
75
+ icon={<Leaf size={20} />}
76
+ accent="success"
77
+ />
78
+ <MetricCard
79
+ label="Total distance"
80
+ value={formatKm(summary.totalDistanceKm)}
81
+ icon={<Truck size={20} />}
82
+ accent="blue"
83
+ />
84
+ <MetricCard
85
+ label="Avg intensity"
86
+ value={`${(summary.averageCo2eKgPerKm * 1000).toFixed(1)} g/km`}
87
+ icon={<Leaf size={20} />}
88
+ accent="warning"
89
+ />
90
+ </MetricGrid>
91
+
92
+ {hasData && summary.byVehicle.length > 0 && (
93
+ <div className="space-y-3">
94
+ <h4 className="text-sm font-medium text-text-secondary">By vehicle</h4>
95
+ <div className="space-y-2">
96
+ {summary.byVehicle
97
+ .filter((v) => v.totalDistanceKm > 0 || v.co2eKg > 0)
98
+ .slice(0, 5)
99
+ .map((vehicle) => (
100
+ <div
101
+ key={vehicle.vehicleId ?? 'unassigned'}
102
+ className="flex items-center justify-between rounded-lg bg-bg-sunken px-3 py-2"
103
+ >
104
+ <div className="flex items-center gap-2">
105
+ <Truck size={16} className="text-text-secondary" />
106
+ <span className="text-sm font-medium text-text-primary">
107
+ {vehicle.vehicleNumber ?? 'Unassigned'}
108
+ </span>
109
+ </div>
110
+ <div className="text-right">
111
+ <p className="text-sm font-medium text-text-primary">
112
+ {formatKg(vehicle.co2eKg)}
113
+ </p>
114
+ <p className="text-xs text-text-secondary">
115
+ {formatKm(vehicle.totalDistanceKm)}
116
+ </p>
117
+ </div>
118
+ </div>
119
+ ))}
120
+ </div>
121
+ </div>
122
+ )}
123
+
124
+ {!hasData && (
125
+ <p className="text-sm text-text-secondary">
126
+ No route distance recorded yet. Assign vehicles and distances to routes to see emissions
127
+ estimates.
128
+ </p>
129
+ )}
130
+ </CardContent>
131
+ </Card>
132
+ );
133
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * UpcomingDemandPanel
3
+ *
4
+ * Surfaces next-week demand forecasts by lane / customer alongside the
5
+ * most recent closed-period variance (BOFF-4393). Pure read-side view
6
+ * over `wspace-movethewheels-svc`'s DemandForecast surface.
7
+ */
8
+
9
+ import { Calendar, TrendingUp, TrendingDown, AlertCircle } from 'lucide-react';
10
+
11
+ import { Card, CardContent, CardHeader, CardTitle, Skeleton } from './ui';
12
+
13
+ import type { ForecastPoint, ForecastVariance } from '../hooks/useDemandForecast';
14
+
15
+ interface UpcomingDemandPanelProps {
16
+ laneForecast: ForecastPoint[];
17
+ customerForecast: ForecastPoint[];
18
+ laneVariance: ForecastVariance[];
19
+ customerVariance: ForecastVariance[];
20
+ isLoading: boolean;
21
+ error: Error | null;
22
+ onRetry: () => void;
23
+ }
24
+
25
+ const formatWeek = (iso: string): string => {
26
+ const date = new Date(iso);
27
+ if (Number.isNaN(date.getTime())) return iso;
28
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
29
+ };
30
+
31
+ const topByVolume = (rows: ForecastPoint[], weekStart: string): ForecastPoint[] => {
32
+ const filtered = rows.filter((row) => row.periodStart === weekStart);
33
+ return [...filtered].sort((a, b) => b.predictedVolume - a.predictedVolume).slice(0, 3);
34
+ };
35
+
36
+ const topByRevenue = (rows: ForecastPoint[], weekStart: string): ForecastPoint[] => {
37
+ const filtered = rows.filter((row) => row.periodStart === weekStart);
38
+ return [...filtered].sort((a, b) => b.predictedRevenue - a.predictedRevenue).slice(0, 3);
39
+ };
40
+
41
+ const totalVolume = (rows: ForecastPoint[], weekStart: string): number =>
42
+ rows
43
+ .filter((row) => row.periodStart === weekStart)
44
+ .reduce((sum, row) => sum + row.predictedVolume, 0);
45
+
46
+ const formatConfidence = (confidence: number): string => {
47
+ if (confidence <= 0) return 'Not enough history';
48
+ return `${Math.round(confidence * 100)}% confidence`;
49
+ };
50
+
51
+ export function UpcomingDemandPanel({
52
+ laneForecast,
53
+ customerForecast,
54
+ laneVariance,
55
+ customerVariance,
56
+ isLoading,
57
+ error,
58
+ onRetry,
59
+ }: UpcomingDemandPanelProps) {
60
+ if (error && !isLoading) {
61
+ return (
62
+ <Card className="border-status-error-border">
63
+ <CardContent className="p-6 text-center space-y-3">
64
+ <p className="text-status-error-text font-medium">Couldn&apos;t load demand forecast</p>
65
+ <p className="text-sm text-text-secondary">{error.message}</p>
66
+ <button
67
+ type="button"
68
+ onClick={onRetry}
69
+ className="text-sm font-medium text-primary hover:underline"
70
+ >
71
+ Retry
72
+ </button>
73
+ </CardContent>
74
+ </Card>
75
+ );
76
+ }
77
+
78
+ if (isLoading) {
79
+ return (
80
+ <Card>
81
+ <CardHeader>
82
+ <CardTitle>Upcoming demand</CardTitle>
83
+ </CardHeader>
84
+ <CardContent className="space-y-4">
85
+ <Skeleton className="h-16 w-full" />
86
+ <Skeleton className="h-16 w-full" />
87
+ <Skeleton className="h-16 w-full" />
88
+ </CardContent>
89
+ </Card>
90
+ );
91
+ }
92
+
93
+ const nextWeekStart = laneForecast[0]?.periodStart ?? customerForecast[0]?.periodStart ?? null;
94
+
95
+ if (!nextWeekStart || laneForecast.length === 0) {
96
+ return (
97
+ <Card>
98
+ <CardHeader>
99
+ <CardTitle className="flex items-center gap-2">
100
+ <Calendar size={18} aria-hidden="true" />
101
+ Upcoming demand
102
+ </CardTitle>
103
+ </CardHeader>
104
+ <CardContent>
105
+ <div className="flex items-start gap-2 rounded-lg border border-border/60 bg-muted/40 p-4 text-sm text-text-secondary">
106
+ <AlertCircle size={16} aria-hidden="true" className="mt-0.5 shrink-0" />
107
+ <p>
108
+ Not enough history to forecast next week yet. Once you have at least 4 weeks of
109
+ completed orders, this panel will surface demand by lane and customer.
110
+ </p>
111
+ </div>
112
+ </CardContent>
113
+ </Card>
114
+ );
115
+ }
116
+
117
+ const topLanes = topByVolume(laneForecast, nextWeekStart);
118
+ const topLanesByRevenue = topByRevenue(laneForecast, nextWeekStart);
119
+ const topCustomers = topByVolume(customerForecast, nextWeekStart);
120
+ const total = totalVolume(laneForecast, nextWeekStart);
121
+
122
+ // Most-recent variance row per scope key — show the lane that overshot /
123
+ // undershot the most in absolute volume terms.
124
+ const mostRecentVariance = [...laneVariance, ...customerVariance]
125
+ .sort((a, b) => Math.abs(b.volumeVariance) - Math.abs(a.volumeVariance))
126
+ .slice(0, 3);
127
+
128
+ return (
129
+ <Card>
130
+ <CardHeader>
131
+ <CardTitle className="flex items-center gap-2">
132
+ <Calendar size={18} aria-hidden="true" />
133
+ Upcoming demand — week of {formatWeek(nextWeekStart)}
134
+ </CardTitle>
135
+ </CardHeader>
136
+ <CardContent className="space-y-4">
137
+ <div className="flex items-baseline justify-between rounded-lg border border-border/60 bg-muted/40 px-4 py-3">
138
+ <div>
139
+ <p className="text-xs uppercase tracking-wide text-text-secondary">
140
+ Predicted jobs (all lanes)
141
+ </p>
142
+ <p className="text-2xl font-bold">{total}</p>
143
+ </div>
144
+ <p className="text-xs text-text-secondary">
145
+ {topLanes[0] ? formatConfidence(topLanes[0].confidence) : ''}
146
+ </p>
147
+ </div>
148
+
149
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
150
+ <ForecastList title="Top lanes (volume)" rows={topLanes} />
151
+ <ForecastList title="Top lanes (revenue)" rows={topLanesByRevenue} />
152
+ <ForecastList title="Top customers (volume)" rows={topCustomers} />
153
+ <VarianceList rows={mostRecentVariance} />
154
+ </div>
155
+ </CardContent>
156
+ </Card>
157
+ );
158
+ }
159
+
160
+ interface ForecastListProps {
161
+ title: string;
162
+ rows: ForecastPoint[];
163
+ }
164
+
165
+ function ForecastList({ title, rows }: ForecastListProps) {
166
+ if (rows.length === 0) {
167
+ return (
168
+ <div className="rounded-lg border border-border/60 p-4">
169
+ <p className="text-sm font-medium mb-2">{title}</p>
170
+ <p className="text-xs text-text-secondary">No forecasts yet for this week.</p>
171
+ </div>
172
+ );
173
+ }
174
+ return (
175
+ <div className="rounded-lg border border-border/60 p-4">
176
+ <p className="text-sm font-medium mb-3">{title}</p>
177
+ <ul className="space-y-2">
178
+ {rows.map((row) => (
179
+ <li
180
+ key={`${row.scopeType}-${row.scopeKey}`}
181
+ className="flex items-center justify-between text-sm"
182
+ >
183
+ <span className="truncate font-medium">{row.scopeKey}</span>
184
+ <span className="ml-2 shrink-0 text-text-secondary">
185
+ {row.predictedVolume} jobs ·{' '}
186
+ {new Intl.NumberFormat(undefined, {
187
+ style: 'currency',
188
+ currency: 'USD',
189
+ maximumFractionDigits: 0,
190
+ }).format(row.predictedRevenue)}
191
+ </span>
192
+ </li>
193
+ ))}
194
+ </ul>
195
+ </div>
196
+ );
197
+ }
198
+
199
+ interface VarianceListProps {
200
+ rows: ForecastVariance[];
201
+ }
202
+
203
+ function VarianceList({ rows }: VarianceListProps) {
204
+ return (
205
+ <div className="rounded-lg border border-border/60 p-4">
206
+ <p className="text-sm font-medium mb-3">Recent forecast variance</p>
207
+ {rows.length === 0 ? (
208
+ <p className="text-xs text-text-secondary">No closed periods to compare yet.</p>
209
+ ) : (
210
+ <ul className="space-y-2">
211
+ {rows.map((row) => {
212
+ const isOvershoot = row.volumeVariance > 0;
213
+ const Icon = isOvershoot ? TrendingUp : TrendingDown;
214
+ const tone = isOvershoot
215
+ ? 'text-emerald-600 dark:text-emerald-400'
216
+ : 'text-rose-600 dark:text-rose-400';
217
+ return (
218
+ <li
219
+ key={`${row.scopeType}-${row.scopeKey}-${row.periodStart}`}
220
+ className="flex items-center justify-between text-sm"
221
+ >
222
+ <span className="flex items-center gap-2 truncate">
223
+ <Icon size={14} className={tone} aria-hidden="true" />
224
+ <span className="truncate font-medium">{row.scopeKey}</span>
225
+ </span>
226
+ <span className={`ml-2 shrink-0 ${tone}`}>
227
+ {isOvershoot ? '+' : ''}
228
+ {row.volumeVariance} jobs
229
+ </span>
230
+ </li>
231
+ );
232
+ })}
233
+ </ul>
234
+ )}
235
+ </div>
236
+ );
237
+ }