feat(sprint-5/etapa-4): procesos por recurso + campo empresa en perfil
Feature A — Ver procesos asociados a un recurso: - resource-repo.ts: getProcessCountsPerResource() (una query, N+1 evitado) - resource-repo.ts: getProcessesForResource() (on-demand al expandir) - ResourceCatalogPage: carga conteos en paralelo, columna "Procesos" con badge naranja clicable, fila expandida con links a /workspace/:id Feature B — Campo empresa en perfil: - 013_add_company_to_users.sql: ALTER TABLE users ADD COLUMN IF NOT EXISTS company text - AuthUser: agrega company? opcional - SupabaseAuthService: lee company y name de tabla users en onAuthStateChange - AuthContext: agrega updateUser() para reflejar cambios sin recargar - ProfileSheet.tsx: Sheet con nombre/empresa editables, email read-only - AppHeader: muestra empresa en dropdown + item "Editar perfil" 528 tests Vitest verdes. Build limpio. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,48 @@ export const supabaseResourceRepo = {
|
||||
return (data ?? []).map((row) => fromRowWithCreator(row as Record<string, unknown>))
|
||||
},
|
||||
|
||||
// Devuelve un map resourceId → cantidad de procesos DISTINTOS que usan ese recurso.
|
||||
// Una sola query para evitar N+1; agregación client-side con Set.
|
||||
async getProcessCountsPerResource(): Promise<Record<string, number>> {
|
||||
const { data, error } = await supabase
|
||||
.from('activity_resource_assignments')
|
||||
.select('resource_id, activity:activities!inner(process_id)')
|
||||
if (error) throw error
|
||||
if (!data) return {}
|
||||
|
||||
const processSets: Record<string, Set<string>> = {}
|
||||
for (const row of data) {
|
||||
const resourceId = row.resource_id as string
|
||||
const processId = (row.activity as unknown as { process_id: string }).process_id
|
||||
if (!processSets[resourceId]) processSets[resourceId] = new Set()
|
||||
processSets[resourceId].add(processId)
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(processSets).map(([k, v]) => [k, v.size])
|
||||
)
|
||||
},
|
||||
|
||||
// Devuelve los procesos distintos que usan el recurso (llamada on-demand al expandir fila).
|
||||
async getProcessesForResource(resourceId: string): Promise<{ id: string; name: string }[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('activity_resource_assignments')
|
||||
.select('activity:activities!inner(process:processes!inner(id, name))')
|
||||
.eq('resource_id', resourceId)
|
||||
if (error) throw error
|
||||
if (!data) return []
|
||||
|
||||
const seen = new Set<string>()
|
||||
const result: { id: string; name: string }[] = []
|
||||
for (const row of data) {
|
||||
const process = (row.activity as unknown as { process: { id: string; name: string } }).process
|
||||
if (!seen.has(process.id)) {
|
||||
seen.add(process.id)
|
||||
result.push({ id: process.id, name: process.name })
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
// No elimina si el recurso tiene asignaciones activas en activity_resource_assignments
|
||||
async delete(resourceId: string): Promise<void> {
|
||||
const { count, error: countError } = await supabase
|
||||
|
||||
Reference in New Issue
Block a user