diff --git a/README.md b/README.md index 7941fe4..6fffc0b 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,6 @@ To create the admin during data seeding instead, set `BBS_SEED_ADMIN_PASSWORD` f - We use JS code generation to work with queries/mutations in our graphql API: -## TODO - -- Stats: Implement users tab - ## TODO-old - Larger pictures diff --git a/frontend/src/Routes.tsx b/frontend/src/Routes.tsx index e7c5200..ec70091 100644 --- a/frontend/src/Routes.tsx +++ b/frontend/src/Routes.tsx @@ -12,6 +12,7 @@ const Register = React.lazy(() => import("./features/register/Register")); const OrderStats = React.lazy(() => import("./features/stats/OrderStats")); const ProductStats = React.lazy(() => import("./features/stats/ProductStats")); const Stats = React.lazy(() => import("./features/stats/Stats")); +const UserStats = React.lazy(() => import("./features/stats/UserStats")); const UserDetail = React.lazy(() => import("./features/users/UserDetail")); const UserList = React.lazy(() => import("./features/users/UserList")); @@ -26,6 +27,7 @@ export const Routes = (): JSX.Element => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/features/stats/StatsView.tsx b/frontend/src/features/stats/StatsView.tsx index 41b0912..3142fcd 100644 --- a/frontend/src/features/stats/StatsView.tsx +++ b/frontend/src/features/stats/StatsView.tsx @@ -25,7 +25,7 @@ export const StatsNav = () => (
  • -
  • diff --git a/frontend/src/features/stats/UserStats.tsx b/frontend/src/features/stats/UserStats.tsx new file mode 100644 index 0000000..196ef94 --- /dev/null +++ b/frontend/src/features/stats/UserStats.tsx @@ -0,0 +1,55 @@ +import { useQuery } from "@apollo/client"; +import { ResponsiveBar } from "@nivo/bar"; +import React from "react"; + +import { GQLError } from "../../components/GQLError"; +import { UserStatsDocument } from "../../types"; +import styles from "./Stats.module.css"; +import { StatsView } from "./StatsView"; +import { chartTheme } from "./chartTheme"; + +const UserStats = () => { + const { data, loading, error } = useQuery(UserStatsDocument); + + if (error) return ; + + const users = (data?.allUsers || []) + .filter((user) => Number(user.profile?.orderSumTotal || 0) > 0) + .map((user) => ({ + name: `${user.firstName}${user.lastName ? ` ${user.lastName[0]}.` : ""}`, + total: Number(user.profile?.orderSumTotal || 0), + })) + .sort((a, b) => b.total - a.total) + .slice(0, 10); + + return ( + +

    Topp 10 etter total kjøpesum

    + {users.length ? ( +
    + `${Number(value).toLocaleString("no")} kr`} + /> +
    + ) : ( +

    Ingen kjøp å vise ennå.

    + )} +
    + ); +}; + +export default UserStats; diff --git a/frontend/src/features/stats/userStats.graphql b/frontend/src/features/stats/userStats.graphql new file mode 100644 index 0000000..ef9f030 --- /dev/null +++ b/frontend/src/features/stats/userStats.graphql @@ -0,0 +1,10 @@ +query userStats { + allUsers { + id + firstName + lastName + profile { + orderSumTotal + } + } +} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 2943281..849568e 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -452,6 +452,11 @@ export type ProductStatsQueryVariables = Exact<{ [key: string]: never; }>; export type ProductStatsQuery = { __typename: 'Query', productStats: Array<{ __typename: 'ProductOrderStat', productName: string, data: Array<{ __typename: 'Point', x: string, y: number }> }> }; +export type UserStatsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type UserStatsQuery = { __typename: 'Query', allUsers: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', orderSumTotal: string } | null }> }; + export type UserDetailQueryVariables = Exact<{ userId: string | number; }>; @@ -474,4 +479,5 @@ export const CreateOrderDocument = {"kind":"Document","definitions":[{"kind":"Op export const AllProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProductStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userCounts"}}]}}]}}]} as unknown as DocumentNode; export const OrderStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"yearly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"monthly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"daily"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hourly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode; export const ProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode; +export const UserStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode; export const UserDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}},{"kind":"Field","name":{"kind":"Name","value":"topMonths"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"sum"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"productOrderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file