Headers sticky con marca y acciones, contenedores max-w-[1280px], grids de contenido con rails laterales sticky (cotizador, mi-dia, detalle de cobranza), stepper vertical en el wizard de alta, toolbar+ tabla unificadas en cobranza, tabs como segmented control en dashboards, chat con barra de entrada fija y preguntas sugeridas, landing con navbar + hero + grid de modulos + footer.
259 lines
9.4 KiB
TypeScript
259 lines
9.4 KiB
TypeScript
import { useEffect, useState, type FormEvent } from 'react'
|
|
import { ErrorLogin, getSesion, login, logout, type UsuarioSesion } from '../services/auth'
|
|
import { getMe, getUsuariosMonday, guardarVinculoMonday, type UsuarioMonday } from '../services/api'
|
|
|
|
const labelEstilo = 'block text-xs font-medium text-[#4B5563]'
|
|
const inputEstilo =
|
|
'mt-2 w-full rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm text-[#111827] transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20'
|
|
|
|
/**
|
|
* Sección "Configuración de cuenta": login inline y, con sesión, el vínculo
|
|
* con el usuario de Monday (alimenta round-robin, mis-leads y alertas).
|
|
*/
|
|
export function ConfiguracionCuenta() {
|
|
const [conSesion, setConSesion] = useState(() => getSesion() !== null)
|
|
|
|
// ---------- Login ----------
|
|
const [username, setUsername] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [entrando, setEntrando] = useState(false)
|
|
const [errorLogin, setErrorLogin] = useState('')
|
|
|
|
// ---------- Perfil + usuarios Monday ----------
|
|
const [perfil, setPerfil] = useState<UsuarioSesion | null>(null)
|
|
const [cargandoPerfil, setCargandoPerfil] = useState(false)
|
|
const [usuarios, setUsuarios] = useState<UsuarioMonday[] | null>(null)
|
|
const [errorUsuarios, setErrorUsuarios] = useState('')
|
|
const [seleccion, setSeleccion] = useState('')
|
|
const [guardando, setGuardando] = useState(false)
|
|
const [aviso, setAviso] = useState('')
|
|
const [errorGuardar, setErrorGuardar] = useState('')
|
|
|
|
useEffect(() => {
|
|
if (!conSesion) return
|
|
setCargandoPerfil(true)
|
|
getMe()
|
|
.then((u) => {
|
|
setPerfil(u)
|
|
setSeleccion(u.mondayUserId !== null ? String(u.mondayUserId) : '')
|
|
})
|
|
.catch(() => setErrorGuardar('No se pudo cargar tu perfil.'))
|
|
.finally(() => setCargandoPerfil(false))
|
|
cargarUsuarios()
|
|
}, [conSesion])
|
|
|
|
const cargarUsuarios = () => {
|
|
setErrorUsuarios('')
|
|
getUsuariosMonday()
|
|
.then(setUsuarios)
|
|
.catch(() => {
|
|
setUsuarios(null)
|
|
setErrorUsuarios('No se pudo cargar la lista de usuarios de Monday.')
|
|
})
|
|
}
|
|
|
|
const entrar = async (e: FormEvent) => {
|
|
e.preventDefault()
|
|
setEntrando(true)
|
|
setErrorLogin('')
|
|
try {
|
|
await login(username, password)
|
|
setUsername('')
|
|
setPassword('')
|
|
setConSesion(true)
|
|
} catch (err) {
|
|
setErrorLogin(err instanceof ErrorLogin ? err.message : 'No se pudo iniciar sesión')
|
|
} finally {
|
|
setEntrando(false)
|
|
}
|
|
}
|
|
|
|
const guardar = async (mondayUserId: number | null) => {
|
|
setGuardando(true)
|
|
setAviso('')
|
|
setErrorGuardar('')
|
|
try {
|
|
const actualizado = await guardarVinculoMonday(mondayUserId)
|
|
setPerfil(actualizado)
|
|
setSeleccion(actualizado.mondayUserId !== null ? String(actualizado.mondayUserId) : '')
|
|
setAviso(mondayUserId === null ? 'Vínculo con Monday eliminado' : 'Vínculo con Monday guardado')
|
|
} catch {
|
|
setErrorGuardar('No se pudo guardar el vínculo. Intenta de nuevo.')
|
|
} finally {
|
|
setGuardando(false)
|
|
}
|
|
}
|
|
|
|
const cerrarSesion = () => {
|
|
logout()
|
|
setPerfil(null)
|
|
setUsuarios(null)
|
|
setConSesion(false)
|
|
}
|
|
|
|
const vinculado =
|
|
perfil?.mondayUserId != null ? (usuarios?.find((u) => u.id === perfil.mondayUserId) ?? null) : null
|
|
|
|
return (
|
|
<section
|
|
id="configuracion"
|
|
aria-labelledby="titulo-cuenta"
|
|
className="scroll-mt-20 rounded-xl border border-[#E5E7EB] bg-white p-6 shadow-sm sm:p-8"
|
|
>
|
|
<h2 id="titulo-cuenta" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
|
|
Configuración de cuenta
|
|
</h2>
|
|
|
|
{!conSesion ? (
|
|
<form
|
|
onSubmit={(e) => void entrar(e)}
|
|
className="mt-6 max-w-sm"
|
|
>
|
|
<p className="text-sm leading-relaxed text-[#64748B]">
|
|
Inicia sesión para vincular tu usuario de Monday.
|
|
</p>
|
|
<div className="mt-6">
|
|
<label htmlFor="cuenta-username" className={labelEstilo}>
|
|
Usuario
|
|
</label>
|
|
<input
|
|
id="cuenta-username"
|
|
type="text"
|
|
autoComplete="username"
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
className={inputEstilo}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="mt-4">
|
|
<label htmlFor="cuenta-password" className={labelEstilo}>
|
|
Contraseña
|
|
</label>
|
|
<input
|
|
id="cuenta-password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className={inputEstilo}
|
|
required
|
|
/>
|
|
</div>
|
|
{errorLogin && (
|
|
<p role="alert" className="mt-4 text-sm text-red-600">
|
|
{errorLogin}
|
|
</p>
|
|
)}
|
|
<button
|
|
type="submit"
|
|
disabled={entrando}
|
|
className="mt-6 rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-50"
|
|
>
|
|
{entrando ? 'Entrando…' : 'Entrar'}
|
|
</button>
|
|
</form>
|
|
) : (
|
|
<div className="mt-6 max-w-md">
|
|
{cargandoPerfil && <p className="text-sm text-[#64748B]">Cargando tu perfil…</p>}
|
|
|
|
{perfil && (
|
|
<>
|
|
<p className="text-sm text-[#111827]">
|
|
Sesión: <span className="font-medium">{perfil.nombre}</span>
|
|
</p>
|
|
<p className="mt-2 text-sm text-[#64748B]">
|
|
Vínculo Monday actual:{' '}
|
|
{perfil.mondayUserId === null ? (
|
|
<span className="text-[#94A3B8]">sin vincular</span>
|
|
) : vinculado ? (
|
|
<span className="text-[#111827]">
|
|
{vinculado.nombre} ({vinculado.email})
|
|
</span>
|
|
) : (
|
|
<span className="text-[#111827]">ID {perfil.mondayUserId}</span>
|
|
)}
|
|
</p>
|
|
|
|
{errorUsuarios ? (
|
|
<div className="mt-6">
|
|
<p role="alert" className="text-sm text-red-600">
|
|
{errorUsuarios}
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={cargarUsuarios}
|
|
className="mt-3 rounded-lg border border-[#D1D5DB] bg-white px-4 py-2.5 text-sm font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
|
|
>
|
|
Reintentar
|
|
</button>
|
|
</div>
|
|
) : usuarios === null ? (
|
|
<p className="mt-6 text-sm text-[#64748B]">Cargando usuarios de Monday…</p>
|
|
) : (
|
|
<div className="mt-6">
|
|
<label htmlFor="cuenta-monday" className={labelEstilo}>
|
|
Tu usuario de Monday
|
|
</label>
|
|
<select
|
|
id="cuenta-monday"
|
|
value={seleccion}
|
|
onChange={(e) => setSeleccion(e.target.value)}
|
|
className={inputEstilo}
|
|
>
|
|
<option value="">— Sin vincular —</option>
|
|
{usuarios.map((u) => (
|
|
<option key={u.id} value={String(u.id)}>
|
|
{u.nombre} ({u.email})
|
|
</option>
|
|
))}
|
|
</select>
|
|
<div className="mt-4 flex flex-wrap gap-3">
|
|
<button
|
|
type="button"
|
|
disabled={guardando}
|
|
onClick={() => void guardar(seleccion === '' ? null : Number(seleccion))}
|
|
className="rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-50"
|
|
>
|
|
{guardando ? 'Guardando…' : 'Guardar'}
|
|
</button>
|
|
{perfil.mondayUserId !== null && (
|
|
<button
|
|
type="button"
|
|
disabled={guardando}
|
|
onClick={() => void guardar(null)}
|
|
className="rounded-lg border border-red-300 bg-white px-5 py-2.5 text-sm font-medium text-red-600 transition-all duration-100 ease-out hover:bg-red-50 disabled:opacity-50"
|
|
>
|
|
Desvincular
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{aviso && (
|
|
<p role="status" className="mt-4 text-sm font-medium text-green-600">
|
|
{aviso}
|
|
</p>
|
|
)}
|
|
{errorGuardar && (
|
|
<p role="alert" className="mt-4 text-sm text-red-600">
|
|
{errorGuardar}
|
|
</p>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={cerrarSesion}
|
|
className="mt-8 rounded-lg border border-[#D1D5DB] bg-white px-4 py-2.5 text-sm font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
|
|
>
|
|
Cerrar sesión
|
|
</button>
|
|
</div>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|