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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Added
|
||||||
- GlobalSettingsPanel: selector de área reemplaza campo libre "Cliente" (Sprint 8)
|
- 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)
|
- WorkspacePage topbar: display de `areaName` de solo lectura reemplaza edición inline de cliente (Sprint 8)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
interface OrgRow {
|
interface OrgRow {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
is_provider: boolean
|
||||||
created_at: string
|
created_at: string
|
||||||
processes: [{ count: number }]
|
processes: [{ count: number }]
|
||||||
users: [{ count: number }]
|
users: [{ count: number }]
|
||||||
@@ -41,10 +42,16 @@ export function OrganizationsPage() {
|
|||||||
const fetchOrgs = async () => {
|
const fetchOrgs = async () => {
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('organizations')
|
.from('organizations')
|
||||||
.select('id, name, created_at, processes:processes(count), users:users(count)')
|
.select('id, name, is_provider, created_at, processes:processes(count), users:users(count)')
|
||||||
.eq('is_provider', false)
|
|
||||||
.order('name')
|
.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)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +91,7 @@ export function OrganizationsPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between mb-5">
|
<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}>
|
<Button size="sm" onClick={openCreate}>
|
||||||
<Plus className="h-4 w-4 mr-1.5" />
|
<Plus className="h-4 w-4 mr-1.5" />
|
||||||
Nueva organización
|
Nueva organización
|
||||||
@@ -113,7 +120,17 @@ export function OrganizationsPage() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{orgs.map((org) => (
|
{orgs.map((org) => (
|
||||||
<tr key={org.id} className="border-b border-slate-100 last:border-0 hover:bg-slate-50/50 transition-colors">
|
<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">
|
<td className="py-3 px-4 text-right text-slate-600 tabular-nums">
|
||||||
{org.processes[0]?.count ?? 0}
|
{org.processes[0]?.count ?? 0}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState, useMemo } from 'react'
|
import { useEffect, useState, useMemo } from 'react'
|
||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate, useLocation } from '@tanstack/react-router'
|
||||||
import {
|
import {
|
||||||
Upload, Search, Building2, GitBranch,
|
Upload, Search, Building2, GitBranch,
|
||||||
Clock, TrendingUp, BarChart2, FileX2, X, Settings,
|
Clock, TrendingUp, BarChart2, FileX2, X, Settings,
|
||||||
@@ -508,7 +508,7 @@ function HomeView({
|
|||||||
const areaGroups = getAreaGroups(orgProcesses)
|
const areaGroups = getAreaGroups(orgProcesses)
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Header de org — colapsable */}
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleOrg(orgId)}
|
onClick={() => toggleOrg(orgId)}
|
||||||
@@ -731,7 +731,7 @@ function GroupView({
|
|||||||
|
|
||||||
export function LibraryPage() {
|
export function LibraryPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { load, processes } = useLibraryStore()
|
const { load, processes, isLoading } = useLibraryStore()
|
||||||
const { user, isProfileLoaded } = useAuth()
|
const { user, isProfileLoaded } = useAuth()
|
||||||
const [view, setView] = useState<'home' | 'group'>('home')
|
const [view, setView] = useState<'home' | 'group'>('home')
|
||||||
const [activeGroupId, setActiveGroupId] = useState<string | null>(null)
|
const [activeGroupId, setActiveGroupId] = useState<string | null>(null)
|
||||||
@@ -739,11 +739,26 @@ export function LibraryPage() {
|
|||||||
const [ownerFilter, setOwnerFilter] = useState<'all' | 'mine'>('all')
|
const [ownerFilter, setOwnerFilter] = useState<'all' | 'mine'>('all')
|
||||||
const { message: toastMessage, show: showToast } = useToast()
|
const { message: toastMessage, show: showToast } = useToast()
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
const orgParam = useMemo(
|
||||||
|
() => new URLSearchParams(location.search).get('org') ?? undefined,
|
||||||
|
[location.search]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) return // no fetchear hasta tener usuario confirmado (evita race condition con sesión Supabase)
|
if (!user) return // no fetchear hasta tener usuario confirmado (evita race condition con sesión Supabase)
|
||||||
load()
|
load()
|
||||||
}, [load, user?.id])
|
}, [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() {
|
function navigateHome() {
|
||||||
setTransitioning(true)
|
setTransitioning(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -192,8 +192,10 @@ export function WorkspacePage() {
|
|||||||
: currentProcess.orgName ?? 'Biblioteca'
|
: currentProcess.orgName ?? 'Biblioteca'
|
||||||
|
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
// Navega siempre a /library (sin ?org=) hasta que exista una vista filtrada por org
|
void navigate({
|
||||||
void navigate({ to: '/library' })
|
to: '/library',
|
||||||
|
search: { org: currentProcess?.orgId ?? undefined },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label del tab "elemento" según lo seleccionado
|
// Label del tab "elemento" según lo seleccionado
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ const libraryRoute = createRoute({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: '/library',
|
path: '/library',
|
||||||
component: LibraryPage,
|
component: LibraryPage,
|
||||||
|
validateSearch: (search: Record<string, unknown>) => ({
|
||||||
|
org: typeof search.org === 'string' ? search.org : undefined,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const importRoute = createRoute({
|
const importRoute = createRoute({
|
||||||
@@ -134,7 +137,7 @@ const settingsRoute = createRoute({
|
|||||||
beforeLoad: ({ context }) => {
|
beforeLoad: ({ context }) => {
|
||||||
const role = (context as { auth?: { user?: { platformRole?: string } } }).auth?.user?.platformRole
|
const role = (context as { auth?: { user?: { platformRole?: string } } }).auth?.user?.platformRole
|
||||||
if (role === 'client_editor' || role === 'client_viewer') {
|
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', () => ({
|
vi.mock('@tanstack/react-router', () => ({
|
||||||
useNavigate: () => vi.fn(),
|
useNavigate: () => vi.fn(),
|
||||||
useRouterState: () => ({ location: { search: '' } }),
|
useRouterState: () => ({ location: { search: '' } }),
|
||||||
|
useLocation: () => ({ search: '' }),
|
||||||
Link: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
Link: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user