Compare commits
4 Commits
b149e1cd0c
...
d776af5f24
| Author | SHA1 | Date | |
|---|---|---|---|
| d776af5f24 | |||
| cc5563974a | |||
| c6c88501d1 | |||
| 1813a9396a |
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Fixed
|
||||
- OrganizationsPage: InQuality visible en lista con badge naranja — quitar filtro is_provider (Sprint 8)
|
||||
- WorkspacePage: back button pasa ?org=orgId para scroll contextual al volver a biblioteca (Sprint 8)
|
||||
- LibraryPage: scroll automático a sección de org al recibir ?org param desde workspace (Sprint 8)
|
||||
- router.tsx: redirect de settingsRoute pasa search: { org: undefined } para compatibilidad con validateSearch (Sprint 8)
|
||||
|
||||
### Added
|
||||
- GlobalSettingsPanel: selector de área reemplaza campo libre "Cliente" (Sprint 8)
|
||||
- WorkspacePage topbar: display de `areaName` de solo lectura reemplaza edición inline de cliente (Sprint 8)
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
interface OrgRow {
|
||||
id: string
|
||||
name: string
|
||||
is_provider: boolean
|
||||
created_at: string
|
||||
processes: [{ count: number }]
|
||||
users: [{ count: number }]
|
||||
@@ -41,10 +42,16 @@ export function OrganizationsPage() {
|
||||
const fetchOrgs = async () => {
|
||||
const { data } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name, created_at, processes:processes(count), users:users(count)')
|
||||
.eq('is_provider', false)
|
||||
.select('id, name, is_provider, created_at, processes:processes(count), users:users(count)')
|
||||
.order('name')
|
||||
setOrgs((data as OrgRow[] | null) ?? [])
|
||||
|
||||
const sorted = ((data as OrgRow[] | null) ?? []).sort((a, b) => {
|
||||
if (a.is_provider && !b.is_provider) return -1
|
||||
if (!a.is_provider && b.is_provider) return 1
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
|
||||
setOrgs(sorted)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
@@ -84,7 +91,7 @@ export function OrganizationsPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-lg font-semibold text-slate-900">Organizaciones clientes</h2>
|
||||
<h2 className="text-lg font-semibold text-slate-900">Organizaciones</h2>
|
||||
<Button size="sm" onClick={openCreate}>
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
Nueva organización
|
||||
@@ -113,7 +120,17 @@ export function OrganizationsPage() {
|
||||
<tbody>
|
||||
{orgs.map((org) => (
|
||||
<tr key={org.id} className="border-b border-slate-100 last:border-0 hover:bg-slate-50/50 transition-colors">
|
||||
<td className="py-3 px-4 font-medium text-slate-900">{org.name}</td>
|
||||
<td className="py-3 px-4 font-medium text-slate-900">
|
||||
<div className="flex items-center gap-2">
|
||||
{org.name}
|
||||
{org.is_provider && (
|
||||
<span className="text-[10px] font-semibold px-1.5 py-0.5 rounded-full border"
|
||||
style={{ color: '#F59845', background: '#fff8f0', borderColor: '#fcd9aa' }}>
|
||||
InQuality
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-right text-slate-600 tabular-nums">
|
||||
{org.processes[0]?.count ?? 0}
|
||||
</td>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { useNavigate, useLocation } from '@tanstack/react-router'
|
||||
import {
|
||||
Upload, Search, Building2, GitBranch,
|
||||
Clock, TrendingUp, BarChart2, FileX2, X, Settings,
|
||||
@@ -508,7 +508,7 @@ function HomeView({
|
||||
const areaGroups = getAreaGroups(orgProcesses)
|
||||
|
||||
return (
|
||||
<div key={orgId} className="border border-border rounded-xl overflow-hidden">
|
||||
<div key={orgId} id={`org-section-${orgId}`} className="border border-border rounded-xl overflow-hidden">
|
||||
{/* Header de org — colapsable */}
|
||||
<button
|
||||
onClick={() => toggleOrg(orgId)}
|
||||
@@ -731,7 +731,7 @@ function GroupView({
|
||||
|
||||
export function LibraryPage() {
|
||||
const navigate = useNavigate()
|
||||
const { load, processes } = useLibraryStore()
|
||||
const { load, processes, isLoading } = useLibraryStore()
|
||||
const { user, isProfileLoaded } = useAuth()
|
||||
const [view, setView] = useState<'home' | 'group'>('home')
|
||||
const [activeGroupId, setActiveGroupId] = useState<string | null>(null)
|
||||
@@ -739,11 +739,26 @@ export function LibraryPage() {
|
||||
const [ownerFilter, setOwnerFilter] = useState<'all' | 'mine'>('all')
|
||||
const { message: toastMessage, show: showToast } = useToast()
|
||||
|
||||
const location = useLocation()
|
||||
const orgParam = useMemo(
|
||||
() => new URLSearchParams(location.search).get('org') ?? undefined,
|
||||
[location.search]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return // no fetchear hasta tener usuario confirmado (evita race condition con sesión Supabase)
|
||||
load()
|
||||
}, [load, user?.id])
|
||||
|
||||
// Scroll a la sección del org si venimos del workspace
|
||||
useEffect(() => {
|
||||
if (!orgParam || isLoading || processes.length === 0) return
|
||||
requestAnimationFrame(() => {
|
||||
const el = document.getElementById(`org-section-${orgParam}`)
|
||||
el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
})
|
||||
}, [orgParam, isLoading, processes.length])
|
||||
|
||||
function navigateHome() {
|
||||
setTransitioning(true)
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -192,8 +192,10 @@ export function WorkspacePage() {
|
||||
: currentProcess.orgName ?? 'Biblioteca'
|
||||
|
||||
function handleBack() {
|
||||
// Navega siempre a /library (sin ?org=) hasta que exista una vista filtrada por org
|
||||
void navigate({ to: '/library' })
|
||||
void navigate({
|
||||
to: '/library',
|
||||
search: { org: currentProcess?.orgId ?? undefined },
|
||||
})
|
||||
}
|
||||
|
||||
// Label del tab "elemento" según lo seleccionado
|
||||
|
||||
@@ -107,6 +107,9 @@ const libraryRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/library',
|
||||
component: LibraryPage,
|
||||
validateSearch: (search: Record<string, unknown>) => ({
|
||||
org: typeof search.org === 'string' ? search.org : undefined,
|
||||
}),
|
||||
})
|
||||
|
||||
const importRoute = createRoute({
|
||||
@@ -134,7 +137,7 @@ const settingsRoute = createRoute({
|
||||
beforeLoad: ({ context }) => {
|
||||
const role = (context as { auth?: { user?: { platformRole?: string } } }).auth?.user?.platformRole
|
||||
if (role === 'client_editor' || role === 'client_viewer') {
|
||||
throw redirect({ to: '/library' })
|
||||
throw redirect({ to: '/library', search: { org: undefined } })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -74,6 +74,7 @@ vi.mock('@/components/AppHeader', () => ({ AppHeader: () => null }))
|
||||
vi.mock('@tanstack/react-router', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
useRouterState: () => ({ location: { search: '' } }),
|
||||
useLocation: () => ({ search: '' }),
|
||||
Link: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user