diff --git a/src/app/explorer/info.tsx b/src/app/explorer/info.tsx index 5acd300..75681ec 100644 --- a/src/app/explorer/info.tsx +++ b/src/app/explorer/info.tsx @@ -1,13 +1,10 @@ import { useEffect, useMemo, useState } from "react"; import { useParams } from "react-router-dom"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Spinner } from "@/components/ui/spinner"; import { ResourceInstance } from "@/state/fetch"; import { ResourceSchema } from "@/state/openapi"; import { useAppSelector } from "@/hooks/store"; import { selectChildResources, selectHeaders } from "@/state/store"; -import ResourceListPage from "./resource_list"; -import { CustomMethodComponent } from "@/components/custom_method"; +import { ResourceInfo } from "@/components/info/info"; type ResourceProperties = { path: string; @@ -55,68 +52,11 @@ export default function InfoPage(props: InfoPageProps) { .then((instance) => setState(instance)); }, [params, props.resource, parentParams, headers]); - const properties = useMemo(() => { - if (state?.properties) { - const results = []; - for (const [key, value] of Object.entries( - state.properties as ResourceProperties, - )) { - results.push( -

- {key}: {value} -

, - ); - } - return results; - } - return ; - }, [state]); - - const customMethods = useMemo(() => { - return props.resource.customMethods(); - }, [props.resource]); - return ( -
- {/* Resource Instance card. */} - - - - {(state?.properties as ResourceProperties)?.path} - - - {properties} - - - {/* Custom Methods */} - {state && customMethods.length > 0 && ( - - - Custom Methods - - - {customMethods.map((customMethod) => ( - - ))} - - - )} - - {/* Listing Child Resources */} - {childResources.map((childResource) => ( - - - {childResource.plural_name} - - - - - - ))} -
+ ); } diff --git a/src/app/explorer/resource_list.tsx b/src/app/explorer/resource_list.tsx index 6e71750..793ff85 100644 --- a/src/app/explorer/resource_list.tsx +++ b/src/app/explorer/resource_list.tsx @@ -1,12 +1,10 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { Button } from "@/components/ui/button"; -import { Plus, RefreshCw } from "lucide-react"; import { ResourceSchema } from "@/state/openapi"; import { ResourceInstance } from "@/state/fetch"; import { selectHeaders } from "@/state/store"; import { useAppSelector } from "@/hooks/store"; -import { ResourceListTable } from "@/components/resource_list"; +import { ListResourceComponent } from "@/components/list/list"; type ResourceListState = { resources: ResourceInstance[]; @@ -49,41 +47,22 @@ export default function ResourceListPage(props: ResourceListProps) { refreshList(); }, [props, refreshList]); + const handleCreate = useCallback(() => { + navigate( + props.resource.substituteUrlParameters( + props.resource.base_url(), + parentParams, + ) + "/_create", + ); + }, [navigate, props.resource, parentParams]); + return ( -
-
-

- {props.resource.plural_name - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ")} -

-
- - -
-
- -
+ ); } diff --git a/src/components/info/info.tsx b/src/components/info/info.tsx new file mode 100644 index 0000000..36b9d9a --- /dev/null +++ b/src/components/info/info.tsx @@ -0,0 +1,90 @@ +import { useMemo } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { ResourceInstance } from "@/state/fetch"; +import { ResourceSchema } from "@/state/openapi"; +import { CustomMethodComponent } from "@/components/custom_method"; +import ResourceListPage from "@/app/explorer/resource_list"; + +type ResourceProperties = { + path: string; + [key: string]: string | number | boolean; +}; + +type ResourceInfoProps = { + resource: ResourceSchema; + resourceInstance: ResourceInstance | null; + childResources: ResourceSchema[]; +}; + +// ResourceInfo renders the details of a resource instance, its custom methods, +// and listings of its child resources. It is a presentational component and +// does not fetch data or depend on routing state. +export function ResourceInfo(props: ResourceInfoProps) { + const { resource, resourceInstance, childResources } = props; + + const properties = useMemo(() => { + if (resourceInstance?.properties) { + const results = []; + for (const [key, value] of Object.entries( + resourceInstance.properties as ResourceProperties, + )) { + results.push( +

+ {key}: {value} +

, + ); + } + return results; + } + return ; + }, [resourceInstance]); + + const customMethods = useMemo(() => { + return resource.customMethods(); + }, [resource]); + + return ( +
+ {/* Resource Instance card. */} + + + + {(resourceInstance?.properties as ResourceProperties)?.path} + + + {properties} + + + {/* Custom Methods */} + {resourceInstance && customMethods.length > 0 && ( + + + Custom Methods + + + {customMethods.map((customMethod) => ( + + ))} + + + )} + + {/* Listing Child Resources */} + {childResources.map((childResource) => ( + + + {childResource.plural_name} + + + + + + ))} +
+ ); +} diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx new file mode 100644 index 0000000..4f5d4ce --- /dev/null +++ b/src/components/list/list.tsx @@ -0,0 +1,47 @@ +import { Button } from "@/components/ui/button"; +import { Plus, RefreshCw } from "lucide-react"; +import { ResourceSchema } from "@/state/openapi"; +import { ResourceInstance } from "@/state/fetch"; +import { ResourceListTable } from "@/components/resource_list"; + +type ListResourceComponentProps = { + resource: ResourceSchema; + resources: ResourceInstance[]; + parentParams: Map; + onRefresh: () => void; + onCreate: () => void; +}; + +// ListResourceComponent renders the header (title, create, refresh) and +// table for a list of resource instances. It is a presentational component +// and does not fetch data or depend on routing state. +export function ListResourceComponent(props: ListResourceComponentProps) { + const { resource, resources, parentParams, onRefresh, onCreate } = props; + + return ( +
+
+

+ {resource.plural_name + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ")} +

+
+ + +
+
+ +
+ ); +}