Sprint 4 — Etapa 7B completada.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled

This commit is contained in:
2026-06-19 00:20:27 -03:00
parent 892b8b3bc7
commit c738bc5489
6 changed files with 247 additions and 11 deletions

View File

@@ -1,20 +1,27 @@
import { useEffect, useState } from 'react'
import { Users2, Search, Plus, Pencil, Trash2 } from 'lucide-react'
import { useNavigate, useSearch } from '@tanstack/react-router'
import { Users2, Search, Plus, Pencil, Trash2, ArrowLeft, CornerUpLeft } from 'lucide-react'
import { AppHeader } from '@/components/AppHeader'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
import { useAuth } from '@/auth/AuthContext'
import { supabaseResourceRepo, type ResourceWithCreator } from '@/persistence/supabase/resource-repo'
import { formatCurrency } from '@/lib/format'
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
import { formatCostPerHour } from '@/lib/format'
import { RESOURCE_TYPE_LABELS, RESOURCE_TYPE_STYLES, ResourceTypeAvatar } from './resource-display'
import { ResourceFormSheet } from './ResourceFormSheet'
import type { Resource, ResourceType } from '@/domain/types'
export function ResourceCatalogPage() {
const { user } = useAuth()
const navigate = useNavigate()
const search_ = useSearch({ from: '/recursos' })
const fromProcess = (search_ as { fromProcess?: string }).fromProcess
const [fromProcessName, setFromProcessName] = useState<string | null>(null)
const [resources, setResources] = useState<ResourceWithCreator[]>([])
const [isLoading, setIsLoading] = useState(true)
const [search, setSearch] = useState('')
@@ -24,6 +31,15 @@ export function ResourceCatalogPage() {
const [deleteError, setDeleteError] = useState<string | null>(null)
const [confirmingDeleteId, setConfirmingDeleteId] = useState<string | null>(null)
useEffect(() => {
if (!fromProcess) return
supabaseProcessRepo.getById(fromProcess).then((p) => setFromProcessName(p?.name ?? null))
}, [fromProcess])
function backToProcess() {
if (fromProcess) navigate({ to: '/workspace/$processId', params: { processId: fromProcess } })
}
async function load() {
setIsLoading(true)
try {
@@ -92,6 +108,31 @@ export function ResourceCatalogPage() {
</Button>
</div>
{fromProcess && (
<div
className="flex items-center justify-between gap-3 mb-4 px-4 py-3 rounded-lg"
style={{ background: '#FEF3E7', borderLeft: '3px solid #F59845' }}
>
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" className="h-7 w-7 -ml-1" onClick={backToProcess}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<p className="text-[13px] font-medium">
Estás viendo el catálogo desde el proceso {fromProcessName ? `"${fromProcessName}"` : ''}
</p>
<p className="text-[12px] text-muted-foreground">
Seleccioná un recurso y usalo en una actividad al volver al workspace.
</p>
</div>
</div>
<Button variant="outline" size="sm" className="gap-1.5 shrink-0" onClick={backToProcess}>
<ArrowLeft className="h-3.5 w-3.5" />
Volver al proceso
</Button>
</div>
)}
{deleteError && (
<div className="mb-4 px-3 py-2 rounded-lg text-[12px] border border-destructive/30 bg-destructive/10 text-destructive">
{deleteError}
@@ -164,7 +205,7 @@ export function ResourceCatalogPage() {
{RESOURCE_TYPE_LABELS[resource.type]}
</Badge>
</TableCell>
<TableCell className="font-mono text-[13px]">{formatCurrency(resource.costPerHour)}</TableCell>
<TableCell className="font-mono text-[13px]">{formatCostPerHour(resource.costPerHour)}</TableCell>
<TableCell className="text-[13px] text-muted-foreground">{resource.creatorName}</TableCell>
<TableCell className="text-right">
{confirmingDeleteId === resource.id ? (
@@ -179,6 +220,18 @@ export function ResourceCatalogPage() {
</div>
) : (
<div className="flex items-center justify-end gap-1">
{fromProcess && (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground hover:text-foreground" onClick={backToProcess}>
<CornerUpLeft className="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-48 text-xs">
Volver al workspace para asignar este recurso a una actividad
</TooltipContent>
</Tooltip>
)}
<Button variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground hover:text-foreground" onClick={() => startEdit(resource)}>
<Pencil className="h-3.5 w-3.5" />
</Button>