Sprint 4 — Etapa 7B completada.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { useProcessStore } from '@/store/process-store'
|
||||
import { useAuth } from '@/auth/AuthContext'
|
||||
import { formatCurrency } from '@/lib/format'
|
||||
import { formatCurrency, formatCostPerHour } from '@/lib/format'
|
||||
import type { Activity } from '@/domain/types'
|
||||
|
||||
interface ActivityPanelProps {
|
||||
@@ -254,7 +254,7 @@ export function ActivityPanel({ selectedElementId }: ActivityPanelProps) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-slate-700">{resource.name}</p>
|
||||
<p className="text-xs text-slate-400 font-mono">{formatCurrency(resource.costPerHour)}/h</p>
|
||||
<p className="text-xs text-slate-400 font-mono">{formatCostPerHour(resource.costPerHour)}/h</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -318,7 +318,7 @@ export function ActivityPanel({ selectedElementId }: ActivityPanelProps) {
|
||||
<SelectContent>
|
||||
{availableResources.map((r) => (
|
||||
<SelectItem key={r.id} value={r.id} className="text-xs">
|
||||
{r.name} — {formatCurrency(r.costPerHour)}/h
|
||||
{r.name} — {formatCostPerHour(r.costPerHour)}/h
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -5,13 +5,13 @@ import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { useProcessStore } from '@/store/process-store'
|
||||
import { useAuth } from '@/auth/AuthContext'
|
||||
import { formatCurrency } from '@/lib/format'
|
||||
import { formatCostPerHour } from '@/lib/format'
|
||||
import { RESOURCE_TYPE_LABELS, ResourceTypeAvatar } from '@/features/resources/resource-display'
|
||||
import { ResourceFormSheet } from '@/features/resources/ResourceFormSheet'
|
||||
import type { Resource } from '@/domain/types'
|
||||
|
||||
export function ResourcesPanel() {
|
||||
const { activities, resources, addResource } = useProcessStore()
|
||||
const { currentProcess, activities, resources, addResource } = useProcessStore()
|
||||
const { user } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const [sheetOpen, setSheetOpen] = useState(false)
|
||||
@@ -65,7 +65,7 @@ export function ResourcesPanel() {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-xs font-medium text-slate-800 truncate">{resource.name}</p>
|
||||
<p className="text-xs text-slate-500 font-mono shrink-0">{formatCurrency(resource.costPerHour)}/h</p>
|
||||
<p className="text-xs text-slate-500 font-mono shrink-0">{formatCostPerHour(resource.costPerHour)}/h</p>
|
||||
</div>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5">
|
||||
{RESOURCE_TYPE_LABELS[resource.type]} · {usageCount(resource.id)} actividad{usageCount(resource.id) !== 1 ? 'es' : ''}
|
||||
@@ -84,7 +84,10 @@ export function ResourcesPanel() {
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full h-8 text-xs gap-1.5"
|
||||
onClick={() => navigate({ to: '/recursos' })}
|
||||
onClick={() => navigate({
|
||||
to: '/recursos',
|
||||
search: currentProcess ? { fromProcess: currentProcess.id } : undefined,
|
||||
})}
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
Ver catálogo completo
|
||||
|
||||
Reference in New Issue
Block a user