This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useNavigate, useSearch } from '@tanstack/react-router'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { UploadCloud, FileText, Loader2, FlaskConical } from 'lucide-react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
@@ -43,6 +43,15 @@ export function ImportPage() {
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [loadingSampleId, setLoadingSampleId] = useState<string | null>(null)
|
||||
|
||||
// Solo platform_admin y member pueden importar BPMN
|
||||
useEffect(() => {
|
||||
if (!user) return
|
||||
const allowed = ['platform_admin', 'member']
|
||||
if (!allowed.includes(user.platformRole)) {
|
||||
void navigate({ to: '/' })
|
||||
}
|
||||
}, [user, navigate])
|
||||
|
||||
// groupId viene de la URL cuando el usuario importa desde dentro de un grupo
|
||||
const search = useSearch({ from: '/import' })
|
||||
const targetGroupId = (search as { groupId?: string }).groupId ?? null
|
||||
@@ -190,6 +199,9 @@ export function ImportPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
|
||||
if (isClientRole) return null
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex flex-col">
|
||||
<AppHeader />
|
||||
|
||||
@@ -447,8 +447,19 @@ function HomeView({
|
||||
)
|
||||
}
|
||||
|
||||
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
|
||||
|
||||
// Empty state: sin grupos ni procesos (ya terminó de cargar)
|
||||
if (groups.length === 0 && processes.length === 0) {
|
||||
if (isClientRole) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-24 gap-4">
|
||||
<Building2 className="h-16 w-16 text-muted-foreground/30" />
|
||||
<p className="text-base font-medium">No tenés procesos asignados aún</p>
|
||||
<p className="text-[13px] text-muted-foreground">Contactá a tu administrador para que te asigne acceso</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-24 gap-4">
|
||||
<Building2 className="h-16 w-16 text-muted-foreground/30" />
|
||||
@@ -527,8 +538,8 @@ function HomeView({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vista normal — grupos */}
|
||||
{!isSearching && (
|
||||
{/* Vista normal — grupos (solo roles internos) */}
|
||||
{!isSearching && !isClientRole && (
|
||||
<>
|
||||
{/* Tag cloud (wow): visible cuando hay >= 3 tags distintos */}
|
||||
{cloud.length >= 3 && (
|
||||
@@ -564,7 +575,7 @@ function HomeView({
|
||||
)}
|
||||
|
||||
{/* Header sección grupos */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2" data-testid="groups-header">
|
||||
<span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{settings.groupLabel}s
|
||||
</span>
|
||||
@@ -616,6 +627,21 @@ function HomeView({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Vista cliente: lista plana de procesos (sin grupos, RLS garantiza que solo ven los suyos) */}
|
||||
{!isSearching && isClientRole && (
|
||||
<div className="space-y-2">
|
||||
{visibleProcesses.map((p) => (
|
||||
<ProcessCardWithSim
|
||||
key={p.id}
|
||||
process={p}
|
||||
currentUserId={currentUserId}
|
||||
currentUserName={currentUserName}
|
||||
onShared={onShared}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -788,6 +814,7 @@ export function LibraryPage() {
|
||||
const currentUserId = user!.id
|
||||
const currentUserName = user!.name
|
||||
const totalGroups = groups.length
|
||||
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -833,14 +860,16 @@ export function LibraryPage() {
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleImport(view === 'group' && activeGroupId ? activeGroupId : undefined)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-white transition-opacity hover:opacity-90"
|
||||
style={{ background: '#F59845' }}
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
Importar BPMN
|
||||
</button>
|
||||
{!isClientRole && (
|
||||
<button
|
||||
onClick={() => handleImport(view === 'group' && activeGroupId ? activeGroupId : undefined)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-white transition-opacity hover:opacity-90"
|
||||
style={{ background: '#F59845' }}
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
Importar BPMN
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,11 +29,13 @@ import { AutomationScenarioNote } from './AutomationScenarioNote'
|
||||
import { MethodologyFooter } from './MethodologyFooter'
|
||||
import { useHeatmapMode } from '@/lib/hooks/useHeatmapMode'
|
||||
import { useSimulationStore } from '@/store/simulation-store'
|
||||
import { useAuth } from '@/auth/AuthContext'
|
||||
import { AppHeader } from '@/components/AppHeader'
|
||||
|
||||
export function ReportPage() {
|
||||
const { processId } = useParams({ from: '/report/$processId' })
|
||||
const navigate = useNavigate()
|
||||
const { user } = useAuth()
|
||||
const { process, simulation, activities, resources, isLoading, error } = useReportData(processId)
|
||||
|
||||
const { toast } = useToast()
|
||||
@@ -108,7 +110,8 @@ export function ReportPage() {
|
||||
|
||||
const [isExportingPdf, setIsExportingPdf] = useState(false)
|
||||
const [isExportingCsv, setIsExportingCsv] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState<'actual' | 'automatizado' | 'roi'>('actual')
|
||||
const defaultTab: 'actual' | 'automatizado' | 'roi' = user?.platformRole === 'client_viewer' ? 'roi' : 'actual'
|
||||
const [activeTab, setActiveTab] = useState<'actual' | 'automatizado' | 'roi'>(defaultTab)
|
||||
|
||||
// Tab "Actual" — cross-linking heatmap ↔ tabla
|
||||
const handleHeatmapClickActual = useCallback((bpmnElementId: string) => {
|
||||
@@ -294,7 +297,7 @@ export function ReportPage() {
|
||||
/>
|
||||
|
||||
{/* ── Tabs ─────────────────────────────────────────────────────── */}
|
||||
<Tabs defaultValue="actual" className="w-full" onValueChange={(v) => setActiveTab(v as typeof activeTab)}>
|
||||
<Tabs defaultValue={defaultTab} className="w-full" onValueChange={(v) => setActiveTab(v as typeof activeTab)}>
|
||||
<TabsList className="mb-6">
|
||||
<TabsTrigger value="actual">Actual</TabsTrigger>
|
||||
|
||||
|
||||
@@ -60,6 +60,13 @@ export function WorkspacePage() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentProcess?.id, loadLatestForProcess])
|
||||
|
||||
// client_viewer no puede editar — redirigir al reporte
|
||||
useEffect(() => {
|
||||
if (user?.platformRole === 'client_viewer') {
|
||||
void navigate({ to: '/report/$processId', params: { processId } })
|
||||
}
|
||||
}, [user, processId, navigate])
|
||||
|
||||
// Validación XOR: todos los gateways exclusivos deben sumar 1.0
|
||||
const xorValidation = useMemo(() => {
|
||||
const invalidIds = gateways
|
||||
@@ -153,7 +160,7 @@ export function WorkspacePage() {
|
||||
|
||||
// isLoading manda siempre, incluso si currentProcess todavía tiene el proceso
|
||||
// anterior en el store (navegación directa entre dos workspaces distintos).
|
||||
if (isLoading) {
|
||||
if (isLoading || user?.platformRole === 'client_viewer') {
|
||||
return <WorkspaceSkeleton />
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user