feat(admin): add view configuration client infrastructure (#13186)
This is part of stacked PRs to add a view configuration feature which will allow users to customize the columns seen in tables in the Medusa Admin dashboard. **What** - Adds client providers, sdk methods and hooks for interacting with the views api.
This commit is contained in:
@@ -34,4 +34,5 @@ export * from "./tags"
|
||||
export * from "./tax-rates"
|
||||
export * from "./tax-regions"
|
||||
export * from "./users"
|
||||
export * from "./views"
|
||||
export * from "./workflow-executions"
|
||||
|
||||
209
packages/admin/dashboard/src/hooks/api/views.tsx
Normal file
209
packages/admin/dashboard/src/hooks/api/views.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions
|
||||
} from "@tanstack/react-query"
|
||||
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
|
||||
const VIEWS_QUERY_KEY = "views" as const
|
||||
export const viewsQueryKeys = queryKeysFactory(VIEWS_QUERY_KEY)
|
||||
|
||||
// Generic hook to get columns for any entity
|
||||
export const useEntityColumns = (entity: string, options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminViewsEntityColumnsResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminViewsEntityColumnsResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.views.columns(entity),
|
||||
queryKey: viewsQueryKeys.list(entity),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
// View Configuration hooks
|
||||
|
||||
// List view configurations for an entity
|
||||
export const useViewConfigurations = (
|
||||
entity: string,
|
||||
query?: HttpTypes.AdminGetViewConfigurationsParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminViewConfigurationListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminViewConfigurationListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.views.listConfigurations(entity, query),
|
||||
queryKey: viewsQueryKeys.list(entity, query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
// Get active view configuration for an entity
|
||||
export const useActiveViewConfiguration = (
|
||||
entity: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminViewConfigurationResponse & {
|
||||
active_view_configuration_id?: string | null
|
||||
is_default_active?: boolean
|
||||
default_type?: "system" | "code"
|
||||
},
|
||||
FetchError,
|
||||
HttpTypes.AdminViewConfigurationResponse & {
|
||||
active_view_configuration_id?: string | null
|
||||
is_default_active?: boolean
|
||||
default_type?: "system" | "code"
|
||||
},
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.views.retrieveActiveConfiguration(entity),
|
||||
queryKey: [viewsQueryKeys.detail(entity), "active"],
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
// Get a specific view configuration
|
||||
export const useViewConfiguration = (
|
||||
entity: string,
|
||||
id: string,
|
||||
query?: Record<string, any>,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminViewConfigurationResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminViewConfigurationResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.views.retrieveConfiguration(entity, id, query),
|
||||
queryKey: viewsQueryKeys.detail(id, query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useCreateViewConfiguration = (
|
||||
entity: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminViewConfigurationResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminCreateViewConfiguration
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: HttpTypes.AdminCreateViewConfiguration) =>
|
||||
sdk.admin.views.createConfiguration(entity, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: viewsQueryKeys.list(entity) })
|
||||
// If set_active was true, also invalidate the active configuration
|
||||
if ((variables as any).set_active) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...viewsQueryKeys.detail(entity, "active")]
|
||||
})
|
||||
}
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateViewConfiguration = (
|
||||
entity: string,
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminViewConfigurationResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminUpdateViewConfiguration
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: HttpTypes.AdminUpdateViewConfiguration) =>
|
||||
sdk.admin.views.updateConfiguration(entity, id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: viewsQueryKeys.list(entity) })
|
||||
queryClient.invalidateQueries({ queryKey: viewsQueryKeys.detail(id) })
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
// Delete view configuration
|
||||
export const useDeleteViewConfiguration = (
|
||||
entity: string,
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminViewConfigurationDeleteResponse,
|
||||
FetchError,
|
||||
void
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: () => sdk.admin.views.deleteConfiguration(entity, id),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: viewsQueryKeys.list(entity) })
|
||||
queryClient.invalidateQueries({ queryKey: viewsQueryKeys.detail(id) })
|
||||
// Also invalidate active configuration as it might have changed
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...viewsQueryKeys.detail(entity, "active")]
|
||||
})
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useSetActiveViewConfiguration = (
|
||||
entity: string,
|
||||
options?: UseMutationOptions<
|
||||
{ success: boolean },
|
||||
FetchError,
|
||||
string | null
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (viewConfigurationId: string | null) =>
|
||||
sdk.admin.views.setActiveConfiguration(entity, {
|
||||
view_configuration_id: viewConfigurationId
|
||||
}),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...viewsQueryKeys.detail(entity, "active")]
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: viewsQueryKeys.list(entity) })
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
105
packages/admin/dashboard/src/hooks/use-view-configurations.tsx
Normal file
105
packages/admin/dashboard/src/hooks/use-view-configurations.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useMemo } from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { toast } from "@medusajs/ui"
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { useFeatureFlag } from "../providers/feature-flag-provider"
|
||||
import {
|
||||
useViewConfigurations as useViewConfigurationsBase,
|
||||
useActiveViewConfiguration as useActiveViewConfigurationBase,
|
||||
useCreateViewConfiguration as useCreateViewConfigurationBase,
|
||||
useUpdateViewConfiguration as useUpdateViewConfigurationBase,
|
||||
useDeleteViewConfiguration as useDeleteViewConfigurationBase,
|
||||
useSetActiveViewConfiguration as useSetActiveViewConfigurationBase,
|
||||
} from "./api/views"
|
||||
|
||||
// Re-export the type for convenience
|
||||
export type ViewConfiguration = HttpTypes.AdminViewConfigurationResponse
|
||||
|
||||
// Common error handler
|
||||
const handleError = (error: Error, message?: string) => {
|
||||
console.error("View configuration error:", error)
|
||||
|
||||
let errorMessage = message
|
||||
if (!errorMessage) {
|
||||
if (error instanceof FetchError) {
|
||||
errorMessage = error.message
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message
|
||||
} else {
|
||||
errorMessage = "An error occurred"
|
||||
}
|
||||
}
|
||||
|
||||
toast.error(errorMessage)
|
||||
}
|
||||
|
||||
export const useViewConfigurations = (entity: string) => {
|
||||
const isViewConfigEnabled = useFeatureFlag("view_configurations")
|
||||
|
||||
const listViews = useViewConfigurationsBase(entity, { limit: 100 }, {
|
||||
enabled: isViewConfigEnabled && !!entity,
|
||||
|
||||
})
|
||||
|
||||
const activeView = useActiveViewConfigurationBase(entity, {
|
||||
enabled: isViewConfigEnabled && !!entity,
|
||||
})
|
||||
|
||||
// Create view mutation
|
||||
const createView = useCreateViewConfigurationBase(entity, {
|
||||
onSuccess: () => {
|
||||
toast.success(`View created`)
|
||||
},
|
||||
onError: (error) => {
|
||||
handleError(error, "Failed to create view")
|
||||
},
|
||||
})
|
||||
|
||||
// Set active view mutation
|
||||
const setActiveView = useSetActiveViewConfigurationBase(entity, {
|
||||
onSuccess: () => { },
|
||||
onError: (error) => {
|
||||
handleError(error, "Failed to update active view")
|
||||
},
|
||||
})
|
||||
|
||||
return useMemo(() => ({
|
||||
isViewConfigEnabled,
|
||||
listViews,
|
||||
activeView,
|
||||
createView,
|
||||
setActiveView,
|
||||
isDefaultViewActive: activeView.data?.is_default_active ?? true,
|
||||
}), [
|
||||
isViewConfigEnabled,
|
||||
listViews,
|
||||
activeView,
|
||||
createView,
|
||||
setActiveView,
|
||||
])
|
||||
}
|
||||
|
||||
export const useViewConfiguration = (entity: string, viewId: string) => {
|
||||
const updateView = useUpdateViewConfigurationBase(entity, viewId, {
|
||||
onSuccess: () => {
|
||||
toast.success(`View updated`)
|
||||
},
|
||||
onError: (error) => {
|
||||
handleError(error, "Failed to update view")
|
||||
},
|
||||
})
|
||||
|
||||
const deleteView = useDeleteViewConfigurationBase(entity, viewId, {
|
||||
onSuccess: () => {
|
||||
toast.success("View deleted")
|
||||
},
|
||||
onError: (error) => {
|
||||
handleError(error, "Failed to delete view")
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
updateView,
|
||||
deleteView,
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { ExtensionProvider } from "./extension-provider"
|
||||
import { I18nProvider } from "./i18n-provider"
|
||||
import { ThemeProvider } from "./theme-provider"
|
||||
import { FeatureFlagProvider } from "./feature-flag-provider"
|
||||
|
||||
type ProvidersProps = PropsWithChildren<{
|
||||
api: DashboardApp["api"]
|
||||
}>
|
||||
|
||||
Reference in New Issue
Block a user