feat: version 4.3.0 -- Sesiones activas y pagina Mi cuenta
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,7 @@ export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowC
|
||||
<tr
|
||||
key={getRowKey(row)}
|
||||
onClick={() => onRowClick?.(row)}
|
||||
onContextMenu={rowActions ? (event) => { event.preventDefault(); event.currentTarget.querySelector('.row-menu-btn')?.click(); } : undefined}
|
||||
tabIndex={onRowClick ? 0 : undefined}
|
||||
onKeyDown={(event) => { if (onRowClick && event.target === event.currentTarget && (event.key === 'Enter' || event.key === ' ')) { event.preventDefault(); onRowClick(row); } }}
|
||||
className={dragEnabled && isDragging(getRowKey(row)) ? 'dragging' : ''}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { LogOut, RefreshCw } from 'lucide-react';
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
import { ListView } from './ListView.jsx';
|
||||
import { Button } from './Button.jsx';
|
||||
import { Badge } from './Badge.jsx';
|
||||
|
||||
export function SessionsList({ load, revoke, revokeAll, onCurrentRevoked, allLabel }) {
|
||||
const { t, lang, openDialog, pushToast } = useUi();
|
||||
const [sessions, setSessions] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const reload = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try { setSessions(await load()); }
|
||||
catch (error) { pushToast({ tone: 'error', message: error.message }); }
|
||||
finally { setLoading(false); }
|
||||
}, [load, pushToast]);
|
||||
useEffect(() => { reload(); }, [reload]);
|
||||
|
||||
function confirm(action, message, current = false) {
|
||||
openDialog({ tone: 'warning', title: t('sessions_close'), message,
|
||||
actions: [
|
||||
{ label: t('common_cancel'), variant: 'standard' },
|
||||
{ label: t('sessions_close'), variant: 'danger', onClick: async () => {
|
||||
try {
|
||||
const result = await action();
|
||||
if (current || result?.current) onCurrentRevoked?.();
|
||||
else { await reload(); pushToast({ tone: 'success', message: t('sessions_closed') }); }
|
||||
} catch (error) { pushToast({ tone: 'error', message: error.message }); }
|
||||
} },
|
||||
] });
|
||||
}
|
||||
const date = (value) => value ? new Date(value.replace(' ', 'T')).toLocaleString(lang) : '—';
|
||||
const columns = [
|
||||
{ key: 'browser', label: t('sessions_device'), render: (row) => <>{row.browser === 'unknown' ? t('sessions_unknown') : row.browser} · {row.os === 'unknown' ? t('sessions_unknown') : row.os} {row.current && <Badge color="accent">{t('sessions_current')}</Badge>}</> },
|
||||
{ key: 'ip', label: t('sessions_ip'), render: (row) => row.ip || '—' },
|
||||
{ key: 'created_at', label: t('sessions_started'), render: (row) => date(row.created_at) },
|
||||
{ key: 'last_seen_at', label: t('sessions_seen'), render: (row) => date(row.last_seen_at) },
|
||||
{ key: 'close', label: t('sessions_close'), sortable: false, render: (row) => <Button size="sm" variant="danger" icon={LogOut} onClick={(event) => { event.stopPropagation(); confirm(() => revoke(row.id), t('sessions_confirm'), row.current); }}>{t('sessions_close')}</Button> },
|
||||
];
|
||||
return <div>
|
||||
<div className="toolbar"><div className="toolbar-left">
|
||||
<Button icon={RefreshCw} onClick={reload}>{t('sessions_refresh')}</Button>
|
||||
{sessions.some((session) => allLabel || !session.current) && <Button variant="danger" icon={LogOut} onClick={() => confirm(revokeAll, t('sessions_confirm_all'))}>{allLabel || t('sessions_close_others')}</Button>}
|
||||
</div></div>
|
||||
{loading ? <div className="text-muted">{t('common_loading')}</div> : <ListView columns={columns} rows={sessions} getRowKey={(row) => row.id}
|
||||
rowActions={(row) => [{ label: t('sessions_close'), icon: LogOut, danger: true, onClick: () => confirm(() => revoke(row.id), t('sessions_confirm'), row.current) }]} />}
|
||||
</div>;
|
||||
}
|
||||
Reference in New Issue
Block a user