From 47aedb348147db42fe951f9960d80ed296623361 Mon Sep 17 00:00:00 2001 From: Oliver Levay Date: Tue, 22 Aug 2023 14:53:03 +0200 Subject: [PATCH] :sparkles: Add ability to see members by product --- backend/services/core/graphql.schema.json | 76 ++++ .../core/src/datasources/WebshopAPI.ts | 18 + .../core/src/resolvers/webshopResolvers.ts | 2 + .../services/core/src/schemas/webshop.graphql | 6 + backend/services/core/src/types/graphql.ts | 22 ++ frontend/api/products.graphql | 32 +- .../Webshop/ManageInventoryItem.tsx | 5 +- .../Webshop/ManageProductInventory.tsx | 6 +- .../components/Webshop/MembersByProduct.tsx | 63 +++ frontend/components/Webshop/ProductEditor.tsx | 8 +- frontend/generated/graphql.tsx | 77 ++++ frontend/package-lock.json | 370 +++++++++++++++++- frontend/package.json | 1 + .../pages/webshop/product/[id]/manage.tsx | 3 + 14 files changed, 684 insertions(+), 5 deletions(-) create mode 100644 frontend/components/Webshop/MembersByProduct.tsx diff --git a/backend/services/core/graphql.schema.json b/backend/services/core/graphql.schema.json index ee3271be2..52f1c0d8a 100644 --- a/backend/services/core/graphql.schema.json +++ b/backend/services/core/graphql.schema.json @@ -7339,6 +7339,49 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "MemberByProduct", + "description": null, + "fields": [ + { + "name": "member", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Member", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "userInventoryItem", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UserInventoryItem", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "MemberFilter", @@ -10160,6 +10203,39 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "getMembersByProduct", + "description": null, + "args": [ + { + "name": "productId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "UUID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "MemberByProduct", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "getSubscriptionTypes", "description": null, diff --git a/backend/services/core/src/datasources/WebshopAPI.ts b/backend/services/core/src/datasources/WebshopAPI.ts index ab6c2f007..b79594d2f 100644 --- a/backend/services/core/src/datasources/WebshopAPI.ts +++ b/backend/services/core/src/datasources/WebshopAPI.ts @@ -17,6 +17,7 @@ import { addMinutes } from '../shared/utils'; import * as gql from '../types/graphql'; import * as sql from '../types/webshop'; import { Member } from '../types/database'; +import { convertMember } from './Member'; let transactions = 0; @@ -85,6 +86,23 @@ export default class WebshopAPI extends dbUtils.KnexDataSource { logger.info('Finished checking for expired carts.'); } + getMembersByProduct(ctx: context.UserContext, productId: UUID): Promise { + return this.withAccess('webshop:read', ctx, async () => { + const product = await this.knex(TABLE.PRODUCT).where({ id: productId }).first(); + if (!product) throw new Error('Product not found'); + const inventories = await this.knex(TABLE.PRODUCT_INVENTORY).where({ + product_id: productId, + }); + const userInventoryItems = await this.knex(TABLE.USER_INVENTORY_ITEM).whereIn('product_inventory_id', inventories.map((i) => i.id)); + const studentIds = userInventoryItems.map((i) => i.student_id); + const members = await this.knex('members').whereIn('student_id', studentIds); + return userInventoryItems.map((item) => ({ + member: convertMember(members.find((m) => m.student_id === item.student_id), ctx)!, + userInventoryItem: convertUserInventoryItem(item), + })); + }); + } + getProducts(ctx: context.UserContext, categoryId?: string): Promise { return this.withAccess('webshop:read', ctx, async () => { let query = this.knex(TABLE.PRODUCT); diff --git a/backend/services/core/src/resolvers/webshopResolvers.ts b/backend/services/core/src/resolvers/webshopResolvers.ts index 00e88302c..e09c0e954 100644 --- a/backend/services/core/src/resolvers/webshopResolvers.ts +++ b/backend/services/core/src/resolvers/webshopResolvers.ts @@ -20,6 +20,8 @@ const webshopResolvers: Resolvers = { dataSources.webshopAPI.getPayment({ user, roles }, id), chest: (_parent, { studentId }, { user, roles, dataSources }) => dataSources.webshopAPI.getUserInventory({ user, roles }, studentId), + getMembersByProduct: async (_, { productId }, { user, roles, dataSources }) => + dataSources.webshopAPI.getMembersByProduct({ user, roles }, productId), }, Mutation: { webshop: () => ({}), diff --git a/backend/services/core/src/schemas/webshop.graphql b/backend/services/core/src/schemas/webshop.graphql index 76d2dc4cc..05d5a8b68 100644 --- a/backend/services/core/src/schemas/webshop.graphql +++ b/backend/services/core/src/schemas/webshop.graphql @@ -5,6 +5,7 @@ extend type Query { myCart: Cart payment(id: UUID!): Payment chest(studentId: String!): UserInventory + getMembersByProduct(productId: UUID!): [MemberByProduct] } extend type Mutation { @@ -27,6 +28,11 @@ type WebshopMutations { consumeItem(itemId: UUID!): UserInventory } +type MemberByProduct { + member: Member! + userInventoryItem: UserInventoryItem! +} + enum PaymentStatus { PENDING PAID diff --git a/backend/services/core/src/types/graphql.ts b/backend/services/core/src/types/graphql.ts index 4bbe9b8fa..4a55ff6b3 100644 --- a/backend/services/core/src/types/graphql.ts +++ b/backend/services/core/src/types/graphql.ts @@ -946,6 +946,12 @@ export type MemberMandatesArgs = { onlyActive?: InputMaybe; }; +export type MemberByProduct = { + __typename?: 'MemberByProduct'; + member: Member; + userInventoryItem: UserInventoryItem; +}; + export type MemberFilter = { class_programme?: InputMaybe; class_year?: InputMaybe; @@ -1230,6 +1236,7 @@ export type Query = { event?: Maybe; events?: Maybe; files?: Maybe>; + getMembersByProduct?: Maybe>>; getSubscriptionTypes: Array; governingDocument?: Maybe; governingDocuments: Array; @@ -1346,6 +1353,11 @@ export type QueryFilesArgs = { }; +export type QueryGetMembersByProductArgs = { + productId: Scalars['UUID']['input']; +}; + + export type QueryGoverningDocumentArgs = { id: Scalars['UUID']['input']; }; @@ -2013,6 +2025,7 @@ export type ResolversTypes = ResolversObject<{ MarkdownMutations: ResolverTypeWrapper; MarkdownPayload: ResolverTypeWrapper; Member: ResolverTypeWrapper; + MemberByProduct: ResolverTypeWrapper; MemberFilter: MemberFilter; MemberMutations: ResolverTypeWrapper; MemberPagination: ResolverTypeWrapper; @@ -2153,6 +2166,7 @@ export type ResolversParentTypes = ResolversObject<{ MarkdownMutations: MarkdownMutations; MarkdownPayload: MarkdownPayload; Member: Member; + MemberByProduct: MemberByProduct; MemberFilter: MemberFilter; MemberMutations: MemberMutations; MemberPagination: MemberPagination; @@ -2682,6 +2696,12 @@ export type MemberResolvers; }>; +export type MemberByProductResolvers = ResolversObject<{ + member?: Resolver; + userInventoryItem?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type MemberMutationsResolvers = ResolversObject<{ create?: Resolver, ParentType, ContextType, RequireFields>; ping?: Resolver, ParentType, ContextType, RequireFields>; @@ -2865,6 +2885,7 @@ export type QueryResolvers, ParentType, ContextType, Partial>; events?: Resolver, ParentType, ContextType, Partial>; files?: Resolver>, ParentType, ContextType, RequireFields>; + getMembersByProduct?: Resolver>>, ParentType, ContextType, RequireFields>; getSubscriptionTypes?: Resolver, ParentType, ContextType>; governingDocument?: Resolver, ParentType, ContextType, RequireFields>; governingDocuments?: Resolver, ParentType, ContextType>; @@ -3116,6 +3137,7 @@ export type Resolvers = ResolversObject<{ MarkdownMutations?: MarkdownMutationsResolvers; MarkdownPayload?: MarkdownPayloadResolvers; Member?: MemberResolvers; + MemberByProduct?: MemberByProductResolvers; MemberMutations?: MemberMutationsResolvers; MemberPagination?: MemberPaginationResolvers; Mutation?: MutationResolvers; diff --git a/frontend/api/products.graphql b/frontend/api/products.graphql index 41893cff6..b22cdc096 100644 --- a/frontend/api/products.graphql +++ b/frontend/api/products.graphql @@ -50,6 +50,35 @@ query ProductCategories { } } +query GetMembersByProduct($productId: UUID!) { + getMembersByProduct(productId: $productId) { + member { + id + student_id + first_name + nickname + last_name + picture_path + food_preference + } + userInventoryItem { + id + name + description + paidPrice + imageUrl + variant + category { + id + name + description + } + paidAt + consumedAt + } + } +} + mutation CreateProduct($input: CreateProductInput!) { webshop { createProduct(input: $input) { @@ -152,4 +181,5 @@ mutation DeleteProduct($productId: UUID!) { webshop { deleteProduct(productId: $productId) } -} \ No newline at end of file +} + diff --git a/frontend/components/Webshop/ManageInventoryItem.tsx b/frontend/components/Webshop/ManageInventoryItem.tsx index f933270ed..898c1e57a 100644 --- a/frontend/components/Webshop/ManageInventoryItem.tsx +++ b/frontend/components/Webshop/ManageInventoryItem.tsx @@ -7,8 +7,10 @@ import { useSnackbar } from '~/providers/SnackbarProvider'; export default function ManageInventoryItem({ inventoryItem, + refetch, }: { inventoryItem: ProductQuery['product']['inventory'][number] + refetch: () => Promise }) { const [variant, setVariant] = useState(inventoryItem.variant); const [quantity, setQuantity] = useState(inventoryItem.quantity.toString()); @@ -24,7 +26,8 @@ export default function ManageInventoryItem({ }); const [deleteInventory] = useDeleteInventoryMutation({ - onCompleted: () => { + onCompleted: async () => { + await refetch(); showMessage('Product inventory deleted', 'success'); }, onError: (error) => { diff --git a/frontend/components/Webshop/ManageProductInventory.tsx b/frontend/components/Webshop/ManageProductInventory.tsx index b30006c7e..44144c4c9 100644 --- a/frontend/components/Webshop/ManageProductInventory.tsx +++ b/frontend/components/Webshop/ManageProductInventory.tsx @@ -53,7 +53,11 @@ export default function ManageProductInventory({ { inventory?.map((inventoryItem) => ( - + )) } diff --git a/frontend/components/Webshop/MembersByProduct.tsx b/frontend/components/Webshop/MembersByProduct.tsx new file mode 100644 index 000000000..0c02482d0 --- /dev/null +++ b/frontend/components/Webshop/MembersByProduct.tsx @@ -0,0 +1,63 @@ +import { Stack, Typography } from '@mui/material'; +import MUIDataTable from 'mui-datatables'; +import { GetMembersByProductQuery, useGetMembersByProductQuery } from '~/generated/graphql'; +import LoadingButton from '../LoadingButton'; + +const columns = [ + 'First Name', + 'Last Name', + 'Student ID', + 'Food Preference', + 'Variant', + 'Consumed At', + 'Paid At', +]; + +const options = { + filterType: 'checkbox', +}; + +function createDataItem(item: GetMembersByProductQuery['getMembersByProduct'][number]) { + return [ + item.member.first_name, + item.member.last_name, + item.member.student_id, + item.member.food_preference, + item.userInventoryItem.variant, + item.userInventoryItem.consumedAt, + item.userInventoryItem.paidAt, + ]; +} + +export default function MembersByProduct({ productId }: { productId: string }) { + const { data: graphqlData, refetch } = useGetMembersByProductQuery({ + variables: { + productId, + }, + }); + const data = graphqlData?.getMembersByProduct.map(createDataItem) ?? [ + 'Loading...', + ]; + return ( + +

Members by product

+ + Total purchases: + {' '} + {graphqlData?.getMembersByProduct.length} + + { + await refetch(); + }} + > + Reload data + + +
+ ); +} diff --git a/frontend/components/Webshop/ProductEditor.tsx b/frontend/components/Webshop/ProductEditor.tsx index 0bf6d84bb..6811b6af4 100644 --- a/frontend/components/Webshop/ProductEditor.tsx +++ b/frontend/components/Webshop/ProductEditor.tsx @@ -11,6 +11,7 @@ import { useState } from 'react'; import { useTranslation } from 'next-i18next'; import { DateTime } from 'luxon'; import Link from 'next/link'; +import { useRouter } from 'next/router'; import { CreateProductInput, ProductQuery, @@ -27,6 +28,7 @@ export default function ProductEditor({ existingProduct?: ProductQuery['product']; onFinish: (input: CreateProductInput) => Promise; }) { + const router = useRouter(); const { data: categoriesData } = useProductCategoriesQuery(); const categories = categoriesData?.productCategories || []; const [categoryId, setCategoryId] = useState(existingProduct?.category?.id || ''); @@ -119,8 +121,12 @@ export default function ProductEditor({ maxPerUser: Number(maxPerUser), price: Number(price), releaseDate, - }); + if (!existingProduct) { + router.push('/webshop'); + } else { + router.push(`/webshop/product/${existingProduct.id}/manage`); + } }} > {existingProduct && 'Save Product'} diff --git a/frontend/generated/graphql.tsx b/frontend/generated/graphql.tsx index e393f727d..62d356224 100644 --- a/frontend/generated/graphql.tsx +++ b/frontend/generated/graphql.tsx @@ -944,6 +944,12 @@ export type MemberMandatesArgs = { onlyActive?: InputMaybe; }; +export type MemberByProduct = { + __typename?: 'MemberByProduct'; + member: Member; + userInventoryItem: UserInventoryItem; +}; + export type MemberFilter = { class_programme?: InputMaybe; class_year?: InputMaybe; @@ -1230,6 +1236,7 @@ export type Query = { event?: Maybe; events?: Maybe; files?: Maybe>; + getMembersByProduct?: Maybe>>; getSubscriptionTypes: Array; governingDocument?: Maybe; governingDocuments: Array; @@ -1351,6 +1358,11 @@ export type QueryFilesArgs = { }; +export type QueryGetMembersByProductArgs = { + productId: Scalars['UUID']; +}; + + export type QueryGoverningDocumentArgs = { id: Scalars['UUID']; }; @@ -2826,6 +2838,13 @@ export type ProductCategoriesQueryVariables = Exact<{ [key: string]: never; }>; export type ProductCategoriesQuery = { __typename?: 'Query', productCategories: Array<{ __typename?: 'ProductCategory', id: any, name: string, description: string } | null> }; +export type GetMembersByProductQueryVariables = Exact<{ + productId: Scalars['UUID']; +}>; + + +export type GetMembersByProductQuery = { __typename?: 'Query', getMembersByProduct?: Array<{ __typename?: 'MemberByProduct', member: { __typename?: 'Member', id: any, student_id?: string | null, first_name?: string | null, nickname?: string | null, last_name?: string | null, picture_path?: string | null, food_preference?: string | null }, userInventoryItem: { __typename?: 'UserInventoryItem', id: any, name: string, description: string, paidPrice: number, imageUrl: string, variant?: string | null, paidAt: any, consumedAt?: any | null, category?: { __typename?: 'ProductCategory', id: any, name: string, description: string } | null } } | null> | null }; + export type CreateProductMutationVariables = Exact<{ input: CreateProductInput; }>; @@ -8657,6 +8676,64 @@ export function useProductCategoriesLazyQuery(baseOptions?: Apollo.LazyQueryHook export type ProductCategoriesQueryHookResult = ReturnType; export type ProductCategoriesLazyQueryHookResult = ReturnType; export type ProductCategoriesQueryResult = Apollo.QueryResult; +export const GetMembersByProductDocument = gql` + query GetMembersByProduct($productId: UUID!) { + getMembersByProduct(productId: $productId) { + member { + id + student_id + first_name + nickname + last_name + picture_path + food_preference + } + userInventoryItem { + id + name + description + paidPrice + imageUrl + variant + category { + id + name + description + } + paidAt + consumedAt + } + } +} + `; + +/** + * __useGetMembersByProductQuery__ + * + * To run a query within a React component, call `useGetMembersByProductQuery` and pass it any options that fit your needs. + * When your component renders, `useGetMembersByProductQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @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; + * + * @example + * const { data, loading, error } = useGetMembersByProductQuery({ + * variables: { + * productId: // value for 'productId' + * }, + * }); + */ +export function useGetMembersByProductQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetMembersByProductDocument, options); + } +export function useGetMembersByProductLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetMembersByProductDocument, options); + } +export type GetMembersByProductQueryHookResult = ReturnType; +export type GetMembersByProductLazyQueryHookResult = ReturnType; +export type GetMembersByProductQueryResult = Apollo.QueryResult; export const CreateProductDocument = gql` mutation CreateProduct($input: CreateProductInput!) { webshop { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 62d54cfaa..cb8256815 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -34,6 +34,7 @@ "luxon": "^3.1.1", "markdown-truncate": "^1.1.0", "meilisearch": "^0.32.3", + "mui-datatables": "^4.3.0", "next": "^12.3.3", "next-auth": "^4.22.1", "next-i18next": "^13.2.2", @@ -1095,7 +1096,19 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "node_modules/@babel/runtime-corejs3": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.10.tgz", + "integrity": "sha512-IcixfV2Jl3UrqZX4c81+7lVg5++2ufYJyAFW3Aux/ZTvY6LVYYhJ9rMgnbX0zGVq6eqfVpnoatTjZdVki/GmWA==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" @@ -5485,6 +5498,16 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-js-pure": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.1.tgz", + "integrity": "sha512-f52QZwkFVDPf7UEQZGHKx6NYxsxmVGJe5DIvbzOdRMJlmT6yv0KDjR8rmy3ngr/t5wU54c7Sp/qIJH0ppbhVpQ==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -7322,6 +7345,56 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -8230,6 +8303,38 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/mui-datatables": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mui-datatables/-/mui-datatables-4.3.0.tgz", + "integrity": "sha512-LFliQwNnnxW03IO+V3q/ORxZsOHkzl53iGogLbjUJzme47hNEN106dM0ie8oMSc0heYJY0J07oZmKm7Xn3X7IQ==", + "dependencies": { + "@babel/runtime-corejs3": "^7.12.1", + "@emotion/cache": "^11.7.1", + "clsx": "^1.1.1", + "lodash.assignwith": "^4.2.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.find": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "lodash.isundefined": "^3.0.1", + "lodash.memoize": "^4.1.2", + "lodash.merge": "^4.6.2", + "prop-types": "^15.7.2", + "react-dnd": "^11.1.3", + "react-dnd-html5-backend": "^11.1.3", + "react-sortable-tree-patch-react-17": "^2.9.0", + "react-to-print": "^2.8.0", + "tss-react": "^3.6.0" + }, + "peerDependencies": { + "@emotion/react": "^11.10.5", + "@mui/icons-material": "^5.11.0", + "@mui/material": "^5.11.0", + "react": "^16.8.0 || ^17.0.2 || ^18.2.0", + "react-dom": "^16.8.0 || ^17.0.2 || ^18.2.0" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -8963,6 +9068,11 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9145,6 +9255,14 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -9231,6 +9349,23 @@ "dnd-core": "^11.1.3" } }, + "node_modules/react-dnd-scrollzone-patch-react-17": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-dnd-scrollzone-patch-react-17/-/react-dnd-scrollzone-patch-react-17-1.0.2.tgz", + "integrity": "sha512-Wfhyc/Y/Veim29REBYm8nMmtDB5IwSmPPhXIuabBgsEa1MrVsuOwK9+7LmuP+mGbDOEP/S6G8+5XvDqPlRFK2g==", + "dependencies": { + "hoist-non-react-statics": "^3.1.0", + "lodash.throttle": "^4.0.1", + "prop-types": "^15.5.9", + "raf": "^3.2.0", + "react-display-name": "^0.2.0" + }, + "peerDependencies": { + "react": "^17.0.1", + "react-dnd": "^11.1.3", + "react-dom": "^17.0.1" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -9491,6 +9626,36 @@ } } }, + "node_modules/react-sortable-tree-patch-react-17": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-sortable-tree-patch-react-17/-/react-sortable-tree-patch-react-17-2.9.0.tgz", + "integrity": "sha512-Ngtdbf78OfjqCxLj7+N+K4zM9d1mQ/tfnUsOfICFDzNa5JHg6AjixAj69ijvz0ykEiA9lYop+0Fm4KCOqCdlKA==", + "dependencies": { + "lodash.isequal": "^4.5.0", + "prop-types": "^15.6.1", + "react": "^17.0.0", + "react-dnd": "^11.1.3", + "react-dnd-html5-backend": "^11.1.3", + "react-dnd-scrollzone-patch-react-17": "^1.0.2", + "react-dom": "^17.0.0", + "react-lifecycles-compat": "^3.0.4", + "react-virtualized": "^9.21.2" + }, + "peerDependencies": { + "react": "^17.0.0", + "react-dnd": "^11.1.3", + "react-dom": "^17.0.0" + } + }, + "node_modules/react-to-print": { + "version": "2.14.13", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.14.13.tgz", + "integrity": "sha512-PqUGgTRZvkyBzWgaZHVBECWPX2nGEc3HOUy6WNXof6HT4yTFI92wtIkqQtr4jfvHbadqjwTgzgh6vgN8KXlWWw==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -9506,6 +9671,23 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-virtualized": { + "version": "9.22.5", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.5.tgz", + "integrity": "sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-virtualized-auto-sizer": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.6.tgz", @@ -10551,6 +10733,26 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, + "node_modules/tss-react": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/tss-react/-/tss-react-3.7.1.tgz", + "integrity": "sha512-dfWUoxBlKZfIG9UC1A2h02OmcE/Ni0itCmmZu94E9g+KyBhKMHKcsKvUm0bNlRqTmYjXiCgPJDmj5fyc8CSrLg==", + "dependencies": { + "@emotion/cache": "*", + "@emotion/serialize": "*", + "@emotion/utils": "*" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/server": "^11.4.0", + "react": "^16.8.0 || ^17.0.2 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/server": { + "optional": true + } + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -11843,6 +12045,22 @@ } } }, + "@babel/runtime-corejs3": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.10.tgz", + "integrity": "sha512-IcixfV2Jl3UrqZX4c81+7lVg5++2ufYJyAFW3Aux/ZTvY6LVYYhJ9rMgnbX0zGVq6eqfVpnoatTjZdVki/GmWA==", + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + } + } + }, "@babel/template": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", @@ -15053,6 +15271,11 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.19.3.tgz", "integrity": "sha512-LeLBMgEGSsG7giquSzvgBrTS7V5UL6ks3eQlUSbN8dJStlLFiRzUm5iqsRyzUB8carhfKjkJ2vzKqE6z1Vga9g==" }, + "core-js-pure": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.1.tgz", + "integrity": "sha512-f52QZwkFVDPf7UEQZGHKx6NYxsxmVGJe5DIvbzOdRMJlmT6yv0KDjR8rmy3ngr/t5wU54c7Sp/qIJH0ppbhVpQ==" + }, "cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -16455,6 +16678,56 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -17019,6 +17292,31 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "mui-datatables": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mui-datatables/-/mui-datatables-4.3.0.tgz", + "integrity": "sha512-LFliQwNnnxW03IO+V3q/ORxZsOHkzl53iGogLbjUJzme47hNEN106dM0ie8oMSc0heYJY0J07oZmKm7Xn3X7IQ==", + "requires": { + "@babel/runtime-corejs3": "^7.12.1", + "@emotion/cache": "^11.7.1", + "clsx": "^1.1.1", + "lodash.assignwith": "^4.2.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.find": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "lodash.isundefined": "^3.0.1", + "lodash.memoize": "^4.1.2", + "lodash.merge": "^4.6.2", + "prop-types": "^15.7.2", + "react-dnd": "^11.1.3", + "react-dnd-html5-backend": "^11.1.3", + "react-sortable-tree-patch-react-17": "^2.9.0", + "react-to-print": "^2.8.0", + "tss-react": "^3.6.0" + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -17524,6 +17822,11 @@ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -17655,6 +17958,14 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -17729,6 +18040,18 @@ "dnd-core": "^11.1.3" } }, + "react-dnd-scrollzone-patch-react-17": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-dnd-scrollzone-patch-react-17/-/react-dnd-scrollzone-patch-react-17-1.0.2.tgz", + "integrity": "sha512-Wfhyc/Y/Veim29REBYm8nMmtDB5IwSmPPhXIuabBgsEa1MrVsuOwK9+7LmuP+mGbDOEP/S6G8+5XvDqPlRFK2g==", + "requires": { + "hoist-non-react-statics": "^3.1.0", + "lodash.throttle": "^4.0.1", + "prop-types": "^15.5.9", + "raf": "^3.2.0", + "react-display-name": "^0.2.0" + } + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -17918,6 +18241,28 @@ "react-is": "^17.0.2" } }, + "react-sortable-tree-patch-react-17": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-sortable-tree-patch-react-17/-/react-sortable-tree-patch-react-17-2.9.0.tgz", + "integrity": "sha512-Ngtdbf78OfjqCxLj7+N+K4zM9d1mQ/tfnUsOfICFDzNa5JHg6AjixAj69ijvz0ykEiA9lYop+0Fm4KCOqCdlKA==", + "requires": { + "lodash.isequal": "^4.5.0", + "prop-types": "^15.6.1", + "react": "^17.0.0", + "react-dnd": "^11.1.3", + "react-dnd-html5-backend": "^11.1.3", + "react-dnd-scrollzone-patch-react-17": "^1.0.2", + "react-dom": "^17.0.0", + "react-lifecycles-compat": "^3.0.4", + "react-virtualized": "^9.21.2" + } + }, + "react-to-print": { + "version": "2.14.13", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.14.13.tgz", + "integrity": "sha512-PqUGgTRZvkyBzWgaZHVBECWPX2nGEc3HOUy6WNXof6HT4yTFI92wtIkqQtr4jfvHbadqjwTgzgh6vgN8KXlWWw==", + "requires": {} + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -17929,6 +18274,19 @@ "prop-types": "^15.6.2" } }, + "react-virtualized": { + "version": "9.22.5", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.5.tgz", + "integrity": "sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ==", + "requires": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + } + }, "react-virtualized-auto-sizer": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.6.tgz", @@ -18711,6 +19069,16 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, + "tss-react": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/tss-react/-/tss-react-3.7.1.tgz", + "integrity": "sha512-dfWUoxBlKZfIG9UC1A2h02OmcE/Ni0itCmmZu94E9g+KyBhKMHKcsKvUm0bNlRqTmYjXiCgPJDmj5fyc8CSrLg==", + "requires": { + "@emotion/cache": "*", + "@emotion/serialize": "*", + "@emotion/utils": "*" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 95c489486..19c4bd43d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,6 +38,7 @@ "luxon": "^3.1.1", "markdown-truncate": "^1.1.0", "meilisearch": "^0.32.3", + "mui-datatables": "^4.3.0", "next": "^12.3.3", "next-auth": "^4.22.1", "next-i18next": "^13.2.2", diff --git a/frontend/pages/webshop/product/[id]/manage.tsx b/frontend/pages/webshop/product/[id]/manage.tsx index fb8ea3cd3..a18462faf 100644 --- a/frontend/pages/webshop/product/[id]/manage.tsx +++ b/frontend/pages/webshop/product/[id]/manage.tsx @@ -2,6 +2,7 @@ import { Divider, Stack } from '@mui/material'; import { useRouter } from 'next/router'; import ManageProduct from '~/components/Webshop/ManageProduct'; import ManageProductInventory from '~/components/Webshop/ManageProductInventory'; +import MembersByProduct from '~/components/Webshop/MembersByProduct'; import genGetProps from '~/functions/genGetServerSideProps'; export default function ManageProductPage() { @@ -13,6 +14,8 @@ export default function ManageProductPage() { + + ); }