From 737accc787961e431c6df2c95e745ed4dd2b73ce Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Thu, 12 Jun 2025 17:06:33 -0700 Subject: [PATCH] Add assets inventory to the new ui Signed-off-by: Sacha Al Himdani --- apps/console2/src/hooks/graph/AssetGraph.ts | 276 ++++++++++ .../AssetGraphCreateMutation.graphql.ts | 297 +++++++++++ .../AssetGraphDeleteMutation.graphql.ts | 132 +++++ .../AssetGraphListQuery.graphql.ts | 480 ++++++++++++++++++ .../AssetGraphNodeQuery.graphql.ts | 289 +++++++++++ .../AssetGraphUpdateMutation.graphql.ts | 244 +++++++++ apps/console2/src/layouts/MainLayout.tsx | 6 + .../organizations/assets/AssetDetailsPage.tsx | 182 +++++++ .../pages/organizations/assets/AssetsPage.tsx | 143 ++++++ .../assets/dialogs/CreateAssetDialog.tsx | 125 +++++ .../assets/utils/badgeVariants.ts | 23 + apps/console2/src/routes.tsx | 4 +- apps/console2/src/routes/assetRoutes.ts | 26 + packages/ui/src/Atoms/Icons/IconBox.tsx | 10 + packages/ui/src/Atoms/Icons/index.tsx | 1 + packages/ui/src/Molecules/Field/Field.tsx | 1 + 16 files changed, 2238 insertions(+), 1 deletion(-) create mode 100644 apps/console2/src/hooks/graph/AssetGraph.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/AssetGraphCreateMutation.graphql.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/AssetGraphDeleteMutation.graphql.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/AssetGraphListQuery.graphql.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/AssetGraphNodeQuery.graphql.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/AssetGraphUpdateMutation.graphql.ts create mode 100644 apps/console2/src/pages/organizations/assets/AssetDetailsPage.tsx create mode 100644 apps/console2/src/pages/organizations/assets/AssetsPage.tsx create mode 100644 apps/console2/src/pages/organizations/assets/dialogs/CreateAssetDialog.tsx create mode 100644 apps/console2/src/pages/organizations/assets/utils/badgeVariants.ts create mode 100644 apps/console2/src/routes/assetRoutes.ts create mode 100644 packages/ui/src/Atoms/Icons/IconBox.tsx diff --git a/apps/console2/src/hooks/graph/AssetGraph.ts b/apps/console2/src/hooks/graph/AssetGraph.ts new file mode 100644 index 000000000..11dd4fa2d --- /dev/null +++ b/apps/console2/src/hooks/graph/AssetGraph.ts @@ -0,0 +1,276 @@ +import { graphql } from "relay-runtime"; +import { useMutation, usePreloadedQuery, type PreloadedQuery } from "react-relay"; +import { useConfirm } from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { promisifyMutation, sprintf } from "@probo/helpers"; +import { useMemo } from "react"; +import type { AssetGraphListQuery } from "./__generated__/AssetGraphListQuery.graphql"; + +export const assetsQuery = graphql` + query AssetGraphListQuery($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + assets(first: 100) @connection(key: "AssetsPage_assets") { + __id + edges { + node { + id + name + amount + criticity + assetType + dataTypesStored + owner { + fullName + } + vendors(first: 50) { + edges { + node { + id + name + websiteUrl + } + } + } + createdAt + } + } + } + peoples(first: 100) { + edges { + node { + id + fullName + } + } + } + } + } + } +`; + +export const assetNodeQuery = graphql` + query AssetGraphNodeQuery($assetId: ID!) { + node(id: $assetId) { + ... on Asset { + id + name + amount + criticity + assetType + dataTypesStored + owner { + id + fullName + } + vendors(first: 50) { + edges { + node { + id + name + websiteUrl + category + } + } + } + createdAt + updatedAt + } + } + } +`; + +export const createAssetMutation = graphql` + mutation AssetGraphCreateMutation( + $input: CreateAssetInput! + $connections: [ID!]! + ) { + createAsset(input: $input) { + assetEdge @prependEdge(connections: $connections) { + node { + id + name + amount + criticity + assetType + dataTypesStored + owner { + id + fullName + } + vendors(first: 10) { + edges { + node { + id + name + websiteUrl + } + } + } + createdAt + } + } + } + } +`; + +export const updateAssetMutation = graphql` + mutation AssetGraphUpdateMutation($input: UpdateAssetInput!) { + updateAsset(input: $input) { + asset { + id + name + amount + criticity + assetType + dataTypesStored + owner { + id + fullName + } + vendors(first: 50) { + edges { + node { + id + name + websiteUrl + } + } + } + updatedAt + } + } + } +`; + +export const deleteAssetMutation = graphql` + mutation AssetGraphDeleteMutation( + $input: DeleteAssetInput! + $connections: [ID!]! + ) { + deleteAsset(input: $input) { + deletedAssetId @deleteEdge(connections: $connections) + } + } +`; + +export const useDeleteAsset = ( + asset: { id?: string; name?: string }, + connectionId: string +) => { + const [mutate] = useMutation(deleteAssetMutation); + const confirm = useConfirm(); + const { __ } = useTranslate(); + + return () => { + if (!asset.id || !asset.name) { + return alert(__("Failed to delete asset: missing id or name")); + } + confirm( + () => + promisifyMutation(mutate)({ + variables: { + input: { + assetId: asset.id!, + }, + connections: [connectionId], + }, + }), + { + message: sprintf( + __( + 'This will permanently delete "%s". This action cannot be undone.' + ), + asset.name + ), + } + ); + }; +}; + +export const useCreateAsset = (connectionId: string) => { + const [mutate] = useMutation(createAssetMutation); + const { __ } = useTranslate(); + + return (input: { + name: string; + amount: number; + criticity: string; + assetType: string; + ownerId: string; + organizationId: string; + vendorIds?: string[]; + dataTypesStored: string; + }) => { + if (!input.name?.trim()) { + return alert(__("Failed to create asset: name is required")); + } + if (!input.ownerId) { + return alert(__("Failed to create asset: owner is required")); + } + if (!input.organizationId) { + return alert(__("Failed to create asset: organization is required")); + } + if (!input.dataTypesStored) { + return alert(__("Failed to create asset: data types stored is required")); + } + + return promisifyMutation(mutate)({ + variables: { + input: { + name: input.name, + amount: input.amount, + criticity: input.criticity, + assetType: input.assetType, + dataTypesStored: input.dataTypesStored || "", + ownerId: input.ownerId, + organizationId: input.organizationId, + vendorIds: input.vendorIds || [], + }, + connections: [connectionId], + }, + }); + }; +}; + +export const useUpdateAsset = () => { + const [mutate] = useMutation(updateAssetMutation); + const { __ } = useTranslate(); + + return (input: { + id: string; + name?: string; + amount?: number; + criticity?: string; + assetType?: string; + dataTypesStored?: string; + ownerId?: string; + vendorIds?: string[]; + }) => { + if (!input.id) { + return alert(__("Failed to update asset: asset ID is required")); + } + + return promisifyMutation(mutate)({ + variables: { + input, + }, + }); + }; +}; + +export const useAssets = (queryRef: PreloadedQuery) => { + const data = usePreloadedQuery(assetsQuery, queryRef); + + return useMemo(() => { + const organization = data.node; + if (!organization || !organization.assets) { + return { assets: [], connectionId: "" }; + } + + return { + assets: organization.assets.edges.map((edge) => edge.node), + connectionId: organization.assets.__id, + }; + }, [data]); +}; diff --git a/apps/console2/src/hooks/graph/__generated__/AssetGraphCreateMutation.graphql.ts b/apps/console2/src/hooks/graph/__generated__/AssetGraphCreateMutation.graphql.ts new file mode 100644 index 000000000..575dc5d2f --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/AssetGraphCreateMutation.graphql.ts @@ -0,0 +1,297 @@ +/** + * @generated SignedSource<<8ddee1e0195c3af2818a78e1224ad297>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type AssetType = "PHYSICAL" | "VIRTUAL"; +export type CriticityLevel = "HIGH" | "LOW" | "MEDIUM"; +export type CreateAssetInput = { + amount: number; + assetType: AssetType; + criticity?: CriticityLevel; + dataTypesStored: string; + name: string; + organizationId: string; + ownerId: string; + vendorIds?: ReadonlyArray | null | undefined; +}; +export type AssetGraphCreateMutation$variables = { + connections: ReadonlyArray; + input: CreateAssetInput; +}; +export type AssetGraphCreateMutation$data = { + readonly createAsset: { + readonly assetEdge: { + readonly node: { + readonly amount: number; + readonly assetType: AssetType; + readonly createdAt: any; + readonly criticity: CriticityLevel; + readonly dataTypesStored: string; + readonly id: string; + readonly name: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly vendors: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly name: string; + readonly websiteUrl: string | null | undefined; + }; + }>; + }; + }; + }; + }; +}; +export type AssetGraphCreateMutation = { + response: AssetGraphCreateMutation$data; + variables: AssetGraphCreateMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "concreteType": "AssetEdge", + "kind": "LinkedField", + "name": "assetEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Asset", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "criticity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assetType", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataTypesStored", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 10 + } + ], + "concreteType": "VendorConnection", + "kind": "LinkedField", + "name": "vendors", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Vendor", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "vendors(first:10)" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "AssetGraphCreateMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateAssetPayload", + "kind": "LinkedField", + "name": "createAsset", + "plural": false, + "selections": [ + (v5/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "AssetGraphCreateMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateAssetPayload", + "kind": "LinkedField", + "name": "createAsset", + "plural": false, + "selections": [ + (v5/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "assetEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "f1d2e4e85879e08931d9d6815cc51ec1", + "id": null, + "metadata": {}, + "name": "AssetGraphCreateMutation", + "operationKind": "mutation", + "text": "mutation AssetGraphCreateMutation(\n $input: CreateAssetInput!\n) {\n createAsset(input: $input) {\n assetEdge {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n id\n fullName\n }\n vendors(first: 10) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "5053ed385668e9f51d916a194ea8936d"; + +export default node; diff --git a/apps/console2/src/hooks/graph/__generated__/AssetGraphDeleteMutation.graphql.ts b/apps/console2/src/hooks/graph/__generated__/AssetGraphDeleteMutation.graphql.ts new file mode 100644 index 000000000..bff31397e --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/AssetGraphDeleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<<067da1978a06c9feadeeff5ae6af7b50>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteAssetInput = { + assetId: string; +}; +export type AssetGraphDeleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteAssetInput; +}; +export type AssetGraphDeleteMutation$data = { + readonly deleteAsset: { + readonly deletedAssetId: string; + }; +}; +export type AssetGraphDeleteMutation = { + response: AssetGraphDeleteMutation$data; + variables: AssetGraphDeleteMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedAssetId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "AssetGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteAssetPayload", + "kind": "LinkedField", + "name": "deleteAsset", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "AssetGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteAssetPayload", + "kind": "LinkedField", + "name": "deleteAsset", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedAssetId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "bbda9e3ed8effba4140c130883a59d07", + "id": null, + "metadata": {}, + "name": "AssetGraphDeleteMutation", + "operationKind": "mutation", + "text": "mutation AssetGraphDeleteMutation(\n $input: DeleteAssetInput!\n) {\n deleteAsset(input: $input) {\n deletedAssetId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "46b040e435edef42987b5524d823f592"; + +export default node; diff --git a/apps/console2/src/hooks/graph/__generated__/AssetGraphListQuery.graphql.ts b/apps/console2/src/hooks/graph/__generated__/AssetGraphListQuery.graphql.ts new file mode 100644 index 000000000..d646a1c32 --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/AssetGraphListQuery.graphql.ts @@ -0,0 +1,480 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type AssetType = "PHYSICAL" | "VIRTUAL"; +export type CriticityLevel = "HIGH" | "LOW" | "MEDIUM"; +export type AssetGraphListQuery$variables = { + organizationId: string; +}; +export type AssetGraphListQuery$data = { + readonly node: { + readonly assets?: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly amount: number; + readonly assetType: AssetType; + readonly createdAt: any; + readonly criticity: CriticityLevel; + readonly dataTypesStored: string; + readonly id: string; + readonly name: string; + readonly owner: { + readonly fullName: string; + }; + readonly vendors: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly name: string; + readonly websiteUrl: string | null | undefined; + }; + }>; + }; + }; + }>; + }; + readonly peoples?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly fullName: string; + readonly id: string; + }; + }>; + }; + }; +}; +export type AssetGraphListQuery = { + response: AssetGraphListQuery$data; + variables: AssetGraphListQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "criticity", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assetType", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataTypesStored", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null +}, +v9 = { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 50 + } + ], + "concreteType": "VendorConnection", + "kind": "LinkedField", + "name": "vendors", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Vendor", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "vendors(first:50)" +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null +}, +v13 = { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null +}, +v14 = { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] +}, +v15 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } +], +v16 = { + "alias": null, + "args": (v15/*: any*/), + "concreteType": "PeopleConnection", + "kind": "LinkedField", + "name": "peoples", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PeopleEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v8/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "peoples(first:100)" +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "AssetGraphListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "assets", + "args": null, + "concreteType": "AssetConnection", + "kind": "LinkedField", + "name": "__AssetsPage_assets_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AssetEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Asset", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v8/*: any*/) + ], + "storageKey": null + }, + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/) + ], + "storageKey": null + }, + (v12/*: any*/) + ], + "storageKey": null + }, + (v13/*: any*/), + (v14/*: any*/) + ], + "storageKey": null + }, + (v16/*: any*/) + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "AssetGraphListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v11/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v15/*: any*/), + "concreteType": "AssetConnection", + "kind": "LinkedField", + "name": "assets", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AssetEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Asset", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v8/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + }, + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/) + ], + "storageKey": null + }, + (v12/*: any*/) + ], + "storageKey": null + }, + (v13/*: any*/), + (v14/*: any*/) + ], + "storageKey": "assets(first:100)" + }, + { + "alias": null, + "args": (v15/*: any*/), + "filters": null, + "handle": "connection", + "key": "AssetsPage_assets", + "kind": "LinkedHandle", + "name": "assets" + }, + (v16/*: any*/) + ], + "type": "Organization", + "abstractKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "2b452fda67547e63fb04fd4c6ae3ec7e", + "id": null, + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "node", + "assets" + ] + } + ] + }, + "name": "AssetGraphListQuery", + "operationKind": "query", + "text": "query AssetGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n assets(first: 100) {\n edges {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n peoples(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "63f6a6df2bdbcd5539e28f892e54c686"; + +export default node; diff --git a/apps/console2/src/hooks/graph/__generated__/AssetGraphNodeQuery.graphql.ts b/apps/console2/src/hooks/graph/__generated__/AssetGraphNodeQuery.graphql.ts new file mode 100644 index 000000000..098ccf515 --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/AssetGraphNodeQuery.graphql.ts @@ -0,0 +1,289 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type AssetType = "PHYSICAL" | "VIRTUAL"; +export type CriticityLevel = "HIGH" | "LOW" | "MEDIUM"; +export type VendorCategory = "ANALYTICS" | "CLOUD_MONITORING" | "CLOUD_PROVIDER" | "COLLABORATION" | "CUSTOMER_SUPPORT" | "DATA_STORAGE_AND_PROCESSING" | "DOCUMENT_MANAGEMENT" | "EMPLOYEE_MANAGEMENT" | "ENGINEERING" | "FINANCE" | "IDENTITY_PROVIDER" | "IT" | "MARKETING" | "OFFICE_OPERATIONS" | "OTHER" | "PASSWORD_MANAGEMENT" | "PRODUCT_AND_DESIGN" | "PROFESSIONAL_SERVICES" | "RECRUITING" | "SALES" | "SECURITY" | "VERSION_CONTROL"; +export type AssetGraphNodeQuery$variables = { + assetId: string; +}; +export type AssetGraphNodeQuery$data = { + readonly node: { + readonly amount?: number; + readonly assetType?: AssetType; + readonly createdAt?: any; + readonly criticity?: CriticityLevel; + readonly dataTypesStored?: string; + readonly id?: string; + readonly name?: string; + readonly owner?: { + readonly fullName: string; + readonly id: string; + }; + readonly updatedAt?: any; + readonly vendors?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly category: VendorCategory; + readonly id: string; + readonly name: string; + readonly websiteUrl: string | null | undefined; + }; + }>; + }; + }; +}; +export type AssetGraphNodeQuery = { + response: AssetGraphNodeQuery$data; + variables: AssetGraphNodeQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "assetId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "assetId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "criticity", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assetType", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataTypesStored", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null +}, +v9 = { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 50 + } + ], + "concreteType": "VendorConnection", + "kind": "LinkedField", + "name": "vendors", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Vendor", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "category", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "vendors(first:50)" +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "AssetGraphNodeQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/) + ], + "type": "Asset", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "AssetGraphNodeQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/) + ], + "type": "Asset", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "cd1d7c48ccf01acee749af3b8948fb9a", + "id": null, + "metadata": {}, + "name": "AssetGraphNodeQuery", + "operationKind": "query", + "text": "query AssetGraphNodeQuery(\n $assetId: ID!\n) {\n node(id: $assetId) {\n __typename\n ... on Asset {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n id\n fullName\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n category\n }\n }\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "47e84c9ea1c9ab9310ad493219c12f3f"; + +export default node; diff --git a/apps/console2/src/hooks/graph/__generated__/AssetGraphUpdateMutation.graphql.ts b/apps/console2/src/hooks/graph/__generated__/AssetGraphUpdateMutation.graphql.ts new file mode 100644 index 000000000..e0a577a98 --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/AssetGraphUpdateMutation.graphql.ts @@ -0,0 +1,244 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type AssetType = "PHYSICAL" | "VIRTUAL"; +export type CriticityLevel = "HIGH" | "LOW" | "MEDIUM"; +export type UpdateAssetInput = { + amount?: number | null | undefined; + assetType?: AssetType | null | undefined; + criticity?: CriticityLevel | null | undefined; + dataTypesStored?: string | null | undefined; + id: string; + name?: string | null | undefined; + ownerId?: string | null | undefined; + vendorIds?: ReadonlyArray | null | undefined; +}; +export type AssetGraphUpdateMutation$variables = { + input: UpdateAssetInput; +}; +export type AssetGraphUpdateMutation$data = { + readonly updateAsset: { + readonly asset: { + readonly amount: number; + readonly assetType: AssetType; + readonly criticity: CriticityLevel; + readonly dataTypesStored: string; + readonly id: string; + readonly name: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly updatedAt: any; + readonly vendors: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly name: string; + readonly websiteUrl: string | null | undefined; + }; + }>; + }; + }; + }; +}; +export type AssetGraphUpdateMutation = { + response: AssetGraphUpdateMutation$data; + variables: AssetGraphUpdateMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v3 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "UpdateAssetPayload", + "kind": "LinkedField", + "name": "updateAsset", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Asset", + "kind": "LinkedField", + "name": "asset", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "criticity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assetType", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataTypesStored", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 50 + } + ], + "concreteType": "VendorConnection", + "kind": "LinkedField", + "name": "vendors", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Vendor", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "vendors(first:50)" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "AssetGraphUpdateMutation", + "selections": (v3/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "AssetGraphUpdateMutation", + "selections": (v3/*: any*/) + }, + "params": { + "cacheID": "f5e4ccc38f842f4965c32676ae407012", + "id": null, + "metadata": {}, + "name": "AssetGraphUpdateMutation", + "operationKind": "mutation", + "text": "mutation AssetGraphUpdateMutation(\n $input: UpdateAssetInput!\n) {\n updateAsset(input: $input) {\n asset {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n id\n fullName\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n updatedAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "257fa8f1a9996808403fa65a06b61ba2"; + +export default node; diff --git a/apps/console2/src/layouts/MainLayout.tsx b/apps/console2/src/layouts/MainLayout.tsx index 4be4c828c..02a5f4dc4 100644 --- a/apps/console2/src/layouts/MainLayout.tsx +++ b/apps/console2/src/layouts/MainLayout.tsx @@ -12,6 +12,7 @@ import { IconStore, IconTodo, IconListStack, + IconBox, Layout, SidebarItem, UserDropdown as UserDropdownRoot, @@ -118,6 +119,11 @@ export function MainLayout() { icon={IconPageTextLine} to={`${prefix}/documents`} /> + ; +}; + +export default function AssetDetailsPage(props: Props) { + const asset = usePreloadedQuery(assetNodeQuery, props.queryRef); + const assetEntry = asset.node; + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + const deleteAsset = useDeleteAsset( + assetEntry, + ConnectionHandler.getConnectionID(organizationId, "AssetsPage_assets") + ); + + const vendors = assetEntry?.vendors?.edges.map((edge: any) => edge.node) ?? []; + const vendorIds = vendors.map((vendor: any) => vendor.id); + + const { control, formState, handleSubmit, register, reset } = useFormWithSchema(updateAssetSchema, { + defaultValues: { + name: assetEntry?.name || "", + amount: assetEntry?.amount || 0, + criticity: assetEntry?.criticity || "LOW", + assetType: assetEntry?.assetType || "VIRTUAL", + dataTypesStored: assetEntry?.dataTypesStored || "", + ownerId: assetEntry?.owner?.id || "", + vendorIds: vendorIds, + }, + }); + + const updateAsset = useUpdateAsset(); + + const onSubmit = handleSubmit(async (formData) => { + try { + await updateAsset({ + id: assetEntry?.id, + ...formData, + }); + reset(formData); + } catch (error) { + console.error("Failed to update asset:", error); + } + }); + + return ( +
+ + +
+
+
{assetEntry?.name}
+ + {assetEntry?.assetType === "PHYSICAL" ? __("Physical") : __("Virtual")} + + + {assetEntry?.criticity} + +
+ + + {__("Delete")} + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ {formState.isDirty && ( + + )} +
+ +
+ ); +} diff --git a/apps/console2/src/pages/organizations/assets/AssetsPage.tsx b/apps/console2/src/pages/organizations/assets/AssetsPage.tsx new file mode 100644 index 000000000..b68e77d45 --- /dev/null +++ b/apps/console2/src/pages/organizations/assets/AssetsPage.tsx @@ -0,0 +1,143 @@ +import { + Button, + IconPlusLarge, + PageHeader, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + Badge, + ActionDropdown, + DropdownItem, + IconTrashCan, + Avatar, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { usePageTitle } from "@probo/hooks"; +import { type PreloadedQuery } from "react-relay"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { CreateAssetDialog } from "./dialogs/CreateAssetDialog"; +import { useDeleteAsset, useAssets } from "../../../hooks/graph/AssetGraph"; +import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql"; +import { faviconUrl } from "@probo/helpers"; +import type { NodeOf } from "/types"; +import { getAssetTypeVariant, getCriticityVariant } from "./utils/badgeVariants"; + +type AssetEntry = NodeOf["assets"]>>; + +type Props = { + queryRef: PreloadedQuery; +}; + +export default function AssetsPage(props: Props) { + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + + const { assets, connectionId } = useAssets(props.queryRef); + + usePageTitle(__("Assets")); + + return ( +
+ + + + + + + + + + + + + + + + + + + {assets.map((entry) => ( + + ))} + +
{__("Name")}{__("Type")}{__("Criticity")}{__("Amount")}{__("Owner")}{__("Vendors")}
+
+ ); +} + +function AssetRow({ + entry, + connectionId, +}: { + entry: AssetEntry; + connectionId: string; +}) { + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + const deleteAsset = useDeleteAsset(entry, connectionId); + const vendors = entry.vendors?.edges.map(edge => edge.node) ?? []; + + return ( + + {entry.name} + + + {entry.assetType === "PHYSICAL" ? __("Physical") : __("Virtual")} + + + + + {entry.criticity} + + + {entry.amount} + {entry.owner?.fullName ?? __("Unassigned")} + + {vendors.length > 0 ? ( +
+ {vendors.slice(0, 3).map((vendor) => ( + + + {vendor.name} + + ))} + {vendors.length > 3 && ( + + +{vendors.length - 3} + + )} +
+ ) : ( + {__("None")} + )} + + + + + {__("Delete")} + + + + + ); +} diff --git a/apps/console2/src/pages/organizations/assets/dialogs/CreateAssetDialog.tsx b/apps/console2/src/pages/organizations/assets/dialogs/CreateAssetDialog.tsx new file mode 100644 index 000000000..95545ad12 --- /dev/null +++ b/apps/console2/src/pages/organizations/assets/dialogs/CreateAssetDialog.tsx @@ -0,0 +1,125 @@ +import { + Button, + Dialog, + DialogContent, + DialogFooter, + Field, + Option, + useDialogRef, + Breadcrumb, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import z from "zod"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { ControlledField } from "/components/form/ControlledField"; +import { PeopleSelectField } from "/components/form/PeopleSelectField"; +import { VendorsMultiSelectField } from "/components/form/VendorsMultiSelectField"; +import { useCreateAsset } from "/hooks/graph/AssetGraph"; + +const schema = z.object({ + name: z.string().min(1, "Name is required"), + amount: z.number().min(1, "Amount is required"), + criticity: z.enum(["LOW", "MEDIUM", "HIGH"]), + assetType: z.enum(["PHYSICAL", "VIRTUAL"]), + ownerId: z.string().min(1, "Owner is required"), + vendorIds: z.array(z.string()).optional(), + dataTypesStored: z.string().min(1, "Data types stored is required"), +}); + +type Props = { + children: React.ReactNode; + connection: string; + organizationId: string; +}; + +export function CreateAssetDialog({ children, connection, organizationId }: Props) { + const { __ } = useTranslate(); + const { control, handleSubmit, register, formState } = useFormWithSchema(schema, { + defaultValues: { + name: "", + amount: 0, + criticity: "LOW", + assetType: "VIRTUAL", + ownerId: "", + vendorIds: [], + }, + }); + const ref = useDialogRef(); + const createAsset = useCreateAsset(connection); + + const onSubmit = handleSubmit(async (data) => { + try { + await createAsset({ + ...data, + organizationId, + }); + ref.current?.close(); + } catch (error) { + console.error("Failed to create asset:", error); + } + }); + + return ( + } + > +
+ + + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/apps/console2/src/pages/organizations/assets/utils/badgeVariants.ts b/apps/console2/src/pages/organizations/assets/utils/badgeVariants.ts new file mode 100644 index 000000000..24f04c8a3 --- /dev/null +++ b/apps/console2/src/pages/organizations/assets/utils/badgeVariants.ts @@ -0,0 +1,23 @@ +export const getAssetTypeVariant = (type: string) => { + switch (type) { + case "PHYSICAL": + return "warning"; + case "VIRTUAL": + return "info"; + default: + return "neutral"; + } +}; + +export const getCriticityVariant = (criticity: string) => { + switch (criticity) { + case "HIGH": + return "danger"; + case "MEDIUM": + return "warning"; + case "LOW": + return "neutral"; + default: + return "neutral"; + } +}; diff --git a/apps/console2/src/routes.tsx b/apps/console2/src/routes.tsx index 2aa3554a9..b4dbe8bd6 100644 --- a/apps/console2/src/routes.tsx +++ b/apps/console2/src/routes.tsx @@ -26,6 +26,7 @@ import { frameworkRoutes } from "./routes/frameworkRoutes.ts"; import { PageError } from "./components/PageError.tsx"; import { taskRoutes } from "./routes/taskRoutes.ts"; import { dataRoutes } from "./routes/dataRoutes.ts"; +import { assetRoutes } from "./routes/assetRoutes.ts"; import { lazy } from "@probo/react-lazy"; function ErrorBoundary() { @@ -94,7 +95,6 @@ const routes = [ }), Component: lazy(() => import("./pages/organizations/SettingsPage")), }, - ...dataRoutes, ...riskRoutes, ...measureRoutes, ...documentsRoutes, @@ -102,6 +102,8 @@ const routes = [ ...vendorRoutes, ...frameworkRoutes, ...taskRoutes, + ...assetRoutes, + ...dataRoutes, { path: "*", Component: PageError, diff --git a/apps/console2/src/routes/assetRoutes.ts b/apps/console2/src/routes/assetRoutes.ts new file mode 100644 index 000000000..70a24b121 --- /dev/null +++ b/apps/console2/src/routes/assetRoutes.ts @@ -0,0 +1,26 @@ +import { loadQuery } from "react-relay"; +import { relayEnvironment } from "/providers/RelayProviders"; +import { PageSkeleton } from "/components/skeletons/PageSkeleton"; +import { lazy } from "@probo/react-lazy"; +import { assetsQuery, assetNodeQuery } from "../hooks/graph/AssetGraph"; + +export const assetRoutes = [ + { + path: "assets", + fallback: PageSkeleton, + queryLoader: (params: Record) => + loadQuery(relayEnvironment, assetsQuery, { organizationId: params.organizationId }), + Component: lazy( + () => import("/pages/organizations/assets/AssetsPage") + ), + }, + { + path: "assets/:assetId", + fallback: PageSkeleton, + queryLoader: (params: Record) => + loadQuery(relayEnvironment, assetNodeQuery, { assetId: params.assetId }), + Component: lazy( + () => import("/pages/organizations/assets/AssetDetailsPage") + ), + }, +]; diff --git a/packages/ui/src/Atoms/Icons/IconBox.tsx b/packages/ui/src/Atoms/Icons/IconBox.tsx new file mode 100644 index 000000000..db8a8d2cf --- /dev/null +++ b/packages/ui/src/Atoms/Icons/IconBox.tsx @@ -0,0 +1,10 @@ +import type {IconProps} from "./type.ts"; + +// https://lucide.dev/icons/box +export function IconBox({size = 24, className}: IconProps) { + return + + + + +} diff --git a/packages/ui/src/Atoms/Icons/index.tsx b/packages/ui/src/Atoms/Icons/index.tsx index 7f55037fb..6f5949452 100644 --- a/packages/ui/src/Atoms/Icons/index.tsx +++ b/packages/ui/src/Atoms/Icons/index.tsx @@ -1,4 +1,5 @@ export { IconArrowUp } from "./IconArrowUp.tsx"; +export { IconBox } from "./IconBox.tsx"; export { IconBell2 } from "./IconBell2.tsx"; export { IconArrowInbox1 } from "./IconArrowInbox1.tsx"; export { IconCircleX } from "./IconCircleX.tsx"; diff --git a/packages/ui/src/Molecules/Field/Field.tsx b/packages/ui/src/Molecules/Field/Field.tsx index 8d9f31e5f..961de4535 100644 --- a/packages/ui/src/Molecules/Field/Field.tsx +++ b/packages/ui/src/Molecules/Field/Field.tsx @@ -20,6 +20,7 @@ type Props = | BaseProps<"email", ComponentProps> | BaseProps<"password", ComponentProps> | BaseProps<"textarea", ComponentProps> + | BaseProps<"number", ComponentProps> | BaseProps<"select", ComponentProps>; const field = tv({