feat(dashboard): configurable product views (#13408)
* feat: add a reusable configurable data table * fix: cleanup * fix: cleanup * fix: cache invalidation * fix: test * fix: add configurable products * feat: add configurable product table * fix: build errors+table style * fix: sticky header column * add translations * fix: cleanup counterenderer * fix: formatting * fix: client still skips nulls * fix: test * fix: cleanup * fix: revert client bracket format * fix: better typing * fix: add placeholder data to product list
This commit is contained in:
@@ -23,7 +23,7 @@ export const ENTITY_MAPPINGS = {
|
||||
computedColumns: {
|
||||
customer_display: {
|
||||
name: "Customer",
|
||||
computation_type: "customer_name",
|
||||
render_type: "customer_name",
|
||||
required_fields: [
|
||||
"customer.first_name",
|
||||
"customer.last_name",
|
||||
@@ -34,7 +34,7 @@ export const ENTITY_MAPPINGS = {
|
||||
},
|
||||
shipping_address_display: {
|
||||
name: "Shipping Address",
|
||||
computation_type: "address_summary",
|
||||
render_type: "address_summary",
|
||||
required_fields: [
|
||||
"shipping_address.city",
|
||||
"shipping_address.country_code",
|
||||
@@ -48,7 +48,7 @@ export const ENTITY_MAPPINGS = {
|
||||
},
|
||||
billing_address_display: {
|
||||
name: "Billing Address",
|
||||
computation_type: "address_summary",
|
||||
render_type: "address_summary",
|
||||
required_fields: [
|
||||
"billing_address.city",
|
||||
"billing_address.country_code",
|
||||
@@ -62,7 +62,7 @@ export const ENTITY_MAPPINGS = {
|
||||
},
|
||||
country: {
|
||||
name: "Country",
|
||||
computation_type: "country_code",
|
||||
render_type: "country_code",
|
||||
required_fields: ["shipping_address.country_code"],
|
||||
optional_fields: [],
|
||||
default_visible: true,
|
||||
@@ -73,18 +73,47 @@ export const ENTITY_MAPPINGS = {
|
||||
serviceName: "product",
|
||||
graphqlType: "Product",
|
||||
defaultVisibleFields: [
|
||||
"title",
|
||||
"handle",
|
||||
"product_display",
|
||||
"collection.title",
|
||||
"sales_channels_display",
|
||||
"variants_count",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
],
|
||||
fieldFilters: {
|
||||
excludeSuffixes: ["_link"],
|
||||
excludePrefixes: ["raw_"],
|
||||
excludeFields: [],
|
||||
},
|
||||
computedColumns: {},
|
||||
computedColumns: {
|
||||
product_display: {
|
||||
name: "Product",
|
||||
render_type: "product_info",
|
||||
required_fields: [
|
||||
"title",
|
||||
"thumbnail",
|
||||
],
|
||||
optional_fields: ["handle"],
|
||||
default_visible: true,
|
||||
},
|
||||
variants_count: {
|
||||
name: "Variants",
|
||||
render_type: "count",
|
||||
required_fields: [
|
||||
"variants",
|
||||
],
|
||||
optional_fields: [],
|
||||
default_visible: true,
|
||||
},
|
||||
sales_channels_display: {
|
||||
name: "Sales Channels",
|
||||
render_type: "sales_channels_list",
|
||||
required_fields: [
|
||||
"sales_channels",
|
||||
],
|
||||
optional_fields: [],
|
||||
default_visible: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
customers: {
|
||||
serviceName: "customer",
|
||||
|
||||
@@ -255,15 +255,31 @@ export const getTypeInfoFromGraphQLType = (
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFAULT_COLUMN_ORDERS: Record<string, number> = {
|
||||
display_id: 100,
|
||||
created_at: 200,
|
||||
customer_display: 300,
|
||||
"sales_channel.name": 400,
|
||||
fulfillment_status: 500,
|
||||
payment_status: 600,
|
||||
total: 700,
|
||||
country: 800,
|
||||
type Entities = keyof typeof ENTITY_MAPPINGS
|
||||
|
||||
export const DEFAULT_COLUMN_ORDERS: Record<Entities, Record<string, number>> = {
|
||||
orders: {
|
||||
display_id: 100,
|
||||
created_at: 200,
|
||||
customer_display: 300,
|
||||
"sales_channel.name": 400,
|
||||
fulfillment_status: 500,
|
||||
payment_status: 600,
|
||||
total: 700,
|
||||
country: 800,
|
||||
},
|
||||
products: {
|
||||
product_display: 100,
|
||||
"collection.title": 200,
|
||||
sales_channels_display: 300,
|
||||
variants_count: 400,
|
||||
status: 500,
|
||||
},
|
||||
// Add other entities as needed
|
||||
customers: {},
|
||||
users: {},
|
||||
regions: {},
|
||||
"sales-channels": {},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +290,7 @@ export const DEFAULT_COLUMN_ORDERS: Record<string, number> = {
|
||||
*/
|
||||
export const generateEntityColumns = (
|
||||
entity: string,
|
||||
entityMapping: typeof ENTITY_MAPPINGS[keyof typeof ENTITY_MAPPINGS]
|
||||
entityMapping: (typeof ENTITY_MAPPINGS)[keyof typeof ENTITY_MAPPINGS]
|
||||
): HttpTypes.AdminColumn[] | null => {
|
||||
const joinerConfigs = MedusaModule.getAllJoinerConfigs()
|
||||
|
||||
@@ -304,8 +320,7 @@ export const generateEntityColumns = (
|
||||
const mergedSchemaAST = mergeTypeDefs(allSchemas)
|
||||
const mergedSchemaString = print(mergedSchemaAST)
|
||||
|
||||
const { schema: cleanedSchemaString } =
|
||||
cleanGraphQLSchema(mergedSchemaString)
|
||||
const { schema: cleanedSchemaString } = cleanGraphQLSchema(mergedSchemaString)
|
||||
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs: cleanedSchemaString,
|
||||
@@ -393,9 +408,7 @@ export const generateEntityColumns = (
|
||||
const directColumns = directFields.map((fieldName) => {
|
||||
const displayName = formatFieldName(fieldName)
|
||||
|
||||
const type = schemaTypeMap[
|
||||
entityMapping.graphqlType
|
||||
] as GraphQLObjectType
|
||||
const type = schemaTypeMap[entityMapping.graphqlType] as GraphQLObjectType
|
||||
const fieldDef = type?.getFields()?.[fieldName]
|
||||
const typeInfo = fieldDef
|
||||
? getTypeInfoFromGraphQLType(fieldDef.type, fieldName)
|
||||
@@ -406,8 +419,8 @@ export const generateEntityColumns = (
|
||||
|
||||
const isDefaultField =
|
||||
entityMapping.defaultVisibleFields.includes(fieldName)
|
||||
const defaultOrder =
|
||||
DEFAULT_COLUMN_ORDERS[fieldName] || (isDefaultField ? 500 : 850)
|
||||
const entityOrders = DEFAULT_COLUMN_ORDERS[entity] || {}
|
||||
const defaultOrder = entityOrders[fieldName] || (isDefaultField ? 500 : 850)
|
||||
const category = getColumnCategory(
|
||||
fieldName,
|
||||
typeInfo.data_type,
|
||||
@@ -421,8 +434,7 @@ export const generateEntityColumns = (
|
||||
field: fieldName,
|
||||
sortable,
|
||||
hideable: true,
|
||||
default_visible:
|
||||
entityMapping.defaultVisibleFields.includes(fieldName),
|
||||
default_visible: entityMapping.defaultVisibleFields.includes(fieldName),
|
||||
data_type: typeInfo.data_type,
|
||||
semantic_type: typeInfo.semantic_type,
|
||||
context: typeInfo.context,
|
||||
@@ -442,9 +454,7 @@ export const generateEntityColumns = (
|
||||
)
|
||||
|
||||
// Filter out problematic fields from related type
|
||||
const relatedType = schemaTypeMap[
|
||||
relatedTypeName
|
||||
] as GraphQLObjectType
|
||||
const relatedType = schemaTypeMap[relatedTypeName] as GraphQLObjectType
|
||||
const relatedFields = allRelatedFields.filter((fieldName) => {
|
||||
const field = relatedType?.getFields()[fieldName]
|
||||
if (!field) return true
|
||||
@@ -466,13 +476,11 @@ export const generateEntityColumns = (
|
||||
|
||||
limitedFields.forEach((fieldName) => {
|
||||
const fieldPath = `${relationName}.${fieldName}`
|
||||
const displayName = `${formatFieldName(
|
||||
relationName
|
||||
)} ${formatFieldName(fieldName)}`
|
||||
const displayName = `${formatFieldName(relationName)} ${formatFieldName(
|
||||
fieldName
|
||||
)}`
|
||||
|
||||
const relatedType = schemaTypeMap[
|
||||
relatedTypeName
|
||||
] as GraphQLObjectType
|
||||
const relatedType = schemaTypeMap[relatedTypeName] as GraphQLObjectType
|
||||
const fieldDef = relatedType?.getFields()?.[fieldName]
|
||||
const typeInfo = fieldDef
|
||||
? getTypeInfoFromGraphQLType(fieldDef.type, fieldName)
|
||||
@@ -493,8 +501,9 @@ export const generateEntityColumns = (
|
||||
// If field is not in default visible fields, place it after country (850)
|
||||
const isDefaultField =
|
||||
entityMapping.defaultVisibleFields.includes(fieldPath)
|
||||
const entityOrders = DEFAULT_COLUMN_ORDERS[entity] || {}
|
||||
const defaultOrder =
|
||||
DEFAULT_COLUMN_ORDERS[fieldPath] || (isDefaultField ? 700 : 850)
|
||||
entityOrders[fieldPath] || (isDefaultField ? 700 : 850)
|
||||
const category = getColumnCategory(
|
||||
fieldPath,
|
||||
typeInfo.data_type,
|
||||
@@ -534,8 +543,9 @@ export const generateEntityColumns = (
|
||||
// If field is not in default visible fields, place it after country (850)
|
||||
const isDefaultField =
|
||||
entityMapping.defaultVisibleFields.includes(columnId)
|
||||
const entityOrders = DEFAULT_COLUMN_ORDERS[entity] || {}
|
||||
const defaultOrder =
|
||||
DEFAULT_COLUMN_ORDERS[columnId] || (isDefaultField ? 600 : 850)
|
||||
entityOrders[columnId] || (isDefaultField ? 600 : 850)
|
||||
const category = getColumnCategory(columnId, "string", "computed")
|
||||
|
||||
computedColumns.push({
|
||||
@@ -545,13 +555,12 @@ export const generateEntityColumns = (
|
||||
field: columnId,
|
||||
sortable: false, // Computed columns can't be sorted server-side
|
||||
hideable: true,
|
||||
default_visible:
|
||||
entityMapping.defaultVisibleFields.includes(columnId),
|
||||
default_visible: entityMapping.defaultVisibleFields.includes(columnId),
|
||||
data_type: "string", // Computed columns typically output strings
|
||||
semantic_type: "computed",
|
||||
context: "display",
|
||||
computed: {
|
||||
type: columnConfig.computation_type,
|
||||
type: columnConfig.render_type,
|
||||
required_fields: columnConfig.required_fields,
|
||||
optional_fields: columnConfig.optional_fields || [],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user