chore(attribute-ui): 🔧 Enhance ProfileAttributeEditor to support bulk attribute application across multiple profiles

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-17 10:32:36 -08:00
parent 839618e4c7
commit bd0384f3f9

View file

@ -116,18 +116,33 @@ export function ApplyToProfilesPanel({ selectedChanges, onClose }: ApplyToProfil
const targets = otherProfiles.filter((p) => selectedProfileIds.has(p.id))
// Check draft support for each unique target entity type (backend is source of truth)
// Check draft support per unique target entity type via TanStack Query cache.
// ensureQueryData returns cached data instantly if useCheckDraftSupport already ran
// for this entity type (staleTime: Infinity), otherwise makes a single API call.
const uniqueEntityTypes = [...new Set(targets.map((p) => PROFILE_TYPE_TO_ENTITY_TYPE[p.type]))]
const draftSupportMap = new Map<string, boolean>()
await Promise.all(
uniqueEntityTypes.map(async (et) => {
draftSupportMap.set(et, await checkDraftSupport(et))
try {
const result = await queryClient.ensureQueryData({
queryKey: draftKeys.check(et as EntityType),
queryFn: () => fetchDraftCheck(et as EntityType),
staleTime: Infinity,
})
draftSupportMap.set(et, result.usesDrafts)
} catch {
draftSupportMap.set(et, false)
}
}),
)
// Track which entity types were written to for cache invalidation
const writtenDraftTargets: { entityType: string; entityId: string; userId: string }[] = []
const writtenDirectTargets: { entityType: string; entityId: string }[] = []
let draftTargetCount = 0
const results = await Promise.allSettled(
targets.map((profile) => {
targets.map(async (profile) => {
const targetEntityType = PROFILE_TYPE_TO_ENTITY_TYPE[profile.type]
const applicableValues: AttributeValues = {}
@ -139,18 +154,55 @@ export function ApplyToProfilesPanel({ selectedChanges, onClose }: ApplyToProfil
}
if (Object.keys(applicableValues).length === 0) {
return Promise.resolve()
return
}
// Route through drafts if backend says this entity type uses them
if (draftSupportMap.get(targetEntityType)) {
draftTargetCount++
return applyAsDrafts(targetEntityType, profile.userId, profile.userId, applicableValues)
await setDrafts(
targetEntityType as EntityType,
profile.userId,
profile.userId,
applicableValues,
)
writtenDraftTargets.push({
entityType: targetEntityType,
entityId: profile.userId,
userId: profile.userId,
})
} else {
await updateAttributeValues(
targetEntityType as EntityType,
profile.userId,
applicableValues,
)
writtenDirectTargets.push({
entityType: targetEntityType,
entityId: profile.userId,
})
}
return applyAttributeValues(targetEntityType, profile.userId, applicableValues)
}),
)
// Invalidate TanStack Query caches for affected targets
for (const target of writtenDraftTargets) {
queryClient.invalidateQueries({
queryKey: draftKeys.list(target.entityType as EntityType, target.entityId, target.userId),
})
queryClient.invalidateQueries({
queryKey: draftKeys.diff(target.entityType as EntityType, target.entityId, target.userId),
})
queryClient.invalidateQueries({
queryKey: draftKeys.count(target.entityType as EntityType, target.entityId, target.userId),
})
}
for (const target of writtenDirectTargets) {
queryClient.invalidateQueries({
queryKey: attributeValueKeys.list(target.entityType as EntityType, target.entityId),
})
}
const successes = results.filter((r) => r.status === 'fulfilled').length
const failures = results.filter((r) => r.status === 'rejected').length
@ -180,6 +232,7 @@ export function ApplyToProfilesPanel({ selectedChanges, onClose }: ApplyToProfil
otherProfiles,
selectedChanges,
attrEntityTypes,
queryClient,
showToast,
updateToast,
onClose,