Sprint 4 — Etapa 7 completada. Build limpio, 521/521 tests verdes. Resumen:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Tipos: Resource.processId? eliminado (vestigial); ResourceType ya tenía los 5 valores correctos, sin cambios. resource-repo.ts: agregado getAllWithCreator() con JOIN a users!created_by (mismo patrón que Etapa 6) y tipo ResourceWithCreator. delete() ahora valida que no haya asignaciones activas en activity_resource_assignments antes de borrar — lanza error explicativo si las hay. Verifiqué ambos contra Supabase real: el JOIN funciona (FK reconocida por PostgREST) y el conteo de guarda también. Nuevo — src/features/resources/: resource-display.tsx — labels, colores/íconos por tipo (Lucide: User/Briefcase/Server/Wrench/Package) y ResourceTypeAvatar compartido ResourceFormSheet.tsx — Sheet reutilizable para crear/editar, usado tanto en el catálogo como en el panel del workspace ResourceCatalogPage.tsx — página /recursos: búsqueda, filtro por tipo, tabla con badge de tipo, costo, "creado por", editar/eliminar con confirmación Nuevo — src/components/ui/sheet.tsx: componente shadcn Sheet (no existía), construido sobre @radix-ui/react-dialog que ya estaba instalado — no agregué dependencias nuevas. ResourcesPanel.tsx rediseñado: ahora solo muestra recursos usados en el proceso actual (derivado de assignedResources), con conteo de actividades por recurso. CRUD completo se mudó a /recursos; el panel solo permite "Nuevo" (catálogo global) y tiene el link "Ver catálogo completo". Router: ruta /recursos registrada. ActivityPanel no se tocó, como indicaba el scope.
This commit is contained in:
@@ -10,6 +10,19 @@ function fromRow(row: Record<string, unknown>): Resource {
|
||||
}
|
||||
}
|
||||
|
||||
// Solo para display — no es parte del dominio puro (mismo patrón que ProcessWithUserNames, Etapa 6)
|
||||
export interface ResourceWithCreator extends Resource {
|
||||
creatorName: string
|
||||
}
|
||||
|
||||
function fromRowWithCreator(row: Record<string, unknown>): ResourceWithCreator {
|
||||
const creator = row.creator as { name: string | null } | null
|
||||
return {
|
||||
...fromRow(row),
|
||||
creatorName: creator?.name ?? 'Desconocido',
|
||||
}
|
||||
}
|
||||
|
||||
export const supabaseResourceRepo = {
|
||||
async save(resource: Resource, userId: string): Promise<void> {
|
||||
const { error } = await supabase.from('resources').upsert({
|
||||
@@ -33,7 +46,27 @@ export const supabaseResourceRepo = {
|
||||
return (data ?? []).map(fromRow)
|
||||
},
|
||||
|
||||
// JOIN con users para resolver "creado por" en una sola query (mismo patrón que Etapa 6)
|
||||
async getAllWithCreator(): Promise<ResourceWithCreator[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('resources')
|
||||
.select('*, creator:users!created_by(name)')
|
||||
.order('name', { ascending: true })
|
||||
if (error) throw error
|
||||
return (data ?? []).map((row) => fromRowWithCreator(row as Record<string, unknown>))
|
||||
},
|
||||
|
||||
// No elimina si el recurso tiene asignaciones activas en activity_resource_assignments
|
||||
async delete(resourceId: string): Promise<void> {
|
||||
const { count, error: countError } = await supabase
|
||||
.from('activity_resource_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('resource_id', resourceId)
|
||||
if (countError) throw countError
|
||||
if ((count ?? 0) > 0) {
|
||||
throw new Error('Este recurso está asignado a actividades — quitá las asignaciones antes de eliminarlo')
|
||||
}
|
||||
|
||||
const { error } = await supabase.from('resources').delete().eq('id', resourceId)
|
||||
if (error) throw error
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user