feat: Add icons to backup status column with rich tooltip
- Add checkmark icon for fresh backups - Add warning triangle for stale backups - Add X icon for critical/never backups - Use consistent Portal-based tooltip matching other columns - Show formatted date, time, and relative age in tooltip
This commit is contained in:
parent
fcb258ac2d
commit
3b38578bce
1 changed files with 96 additions and 16 deletions
|
|
@ -205,6 +205,100 @@ function NetworkInfoCell(props: { ipAddresses: string[]; networkInterfaces: Netw
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backup status cell with Portal tooltip
|
||||||
|
function BackupStatusCell(props: { lastBackup: string | number | null | undefined }) {
|
||||||
|
const [showTooltip, setShowTooltip] = createSignal(false);
|
||||||
|
const [tooltipPos, setTooltipPos] = createSignal({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const info = createMemo(() => getBackupInfo(props.lastBackup));
|
||||||
|
const config = createMemo(() => BACKUP_STATUS_CONFIG[info().status]);
|
||||||
|
|
||||||
|
const handleMouseEnter = (e: MouseEvent) => {
|
||||||
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
|
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top });
|
||||||
|
setShowTooltip(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setShowTooltip(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
class={`inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded cursor-help ${config().bgColor} ${config().color}`}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{/* Status icon */}
|
||||||
|
<Show when={info().status === 'fresh'}>
|
||||||
|
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</Show>
|
||||||
|
<Show when={info().status === 'stale'}>
|
||||||
|
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
</Show>
|
||||||
|
<Show when={info().status === 'critical' || info().status === 'never'}>
|
||||||
|
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</Show>
|
||||||
|
{/* Status text */}
|
||||||
|
{info().status === 'fresh' && 'OK'}
|
||||||
|
{info().status === 'stale' && info().ageFormatted}
|
||||||
|
{info().status === 'critical' && info().ageFormatted}
|
||||||
|
{info().status === 'never' && 'Never'}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Show when={showTooltip()}>
|
||||||
|
<Portal mount={document.body}>
|
||||||
|
<div
|
||||||
|
class="fixed z-[9999] pointer-events-none"
|
||||||
|
style={{
|
||||||
|
left: `${tooltipPos().x}px`,
|
||||||
|
top: `${tooltipPos().y - 8}px`,
|
||||||
|
transform: 'translate(-50%, -100%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="bg-gray-900 dark:bg-gray-800 text-white text-[10px] rounded-md shadow-lg px-2 py-1.5 min-w-[140px] border border-gray-700">
|
||||||
|
<div class="font-medium mb-1 text-gray-300 border-b border-gray-700 pb-1">
|
||||||
|
Backup Status
|
||||||
|
</div>
|
||||||
|
<Show when={info().status !== 'never'}>
|
||||||
|
<div class="py-0.5">
|
||||||
|
<div class="text-gray-400">Last backup</div>
|
||||||
|
<div class="text-gray-200 font-medium">
|
||||||
|
{new Date(props.lastBackup!).toLocaleDateString(undefined, {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-300">
|
||||||
|
{new Date(props.lastBackup!).toLocaleTimeString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-1 mt-1 border-t border-gray-700/50">
|
||||||
|
<span class={config().color}>{info().ageFormatted} ago</span>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={info().status === 'never'}>
|
||||||
|
<div class="py-0.5 text-red-400">
|
||||||
|
No backup has ever been recorded for this guest.
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Column configuration using the priority system
|
// Column configuration using the priority system
|
||||||
export interface GuestColumnDef {
|
export interface GuestColumnDef {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -870,26 +964,12 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
</td>
|
</td>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
{/* Backup Status - NEW */}
|
{/* Backup Status */}
|
||||||
<Show when={isColVisible('backup')}>
|
<Show when={isColVisible('backup')}>
|
||||||
<td class="px-2 py-1 align-middle">
|
<td class="px-2 py-1 align-middle">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<Show when={!props.guest.template}>
|
<Show when={!props.guest.template}>
|
||||||
{(() => {
|
<BackupStatusCell lastBackup={props.guest.lastBackup} />
|
||||||
const info = getBackupInfo(props.guest.lastBackup);
|
|
||||||
const config = BACKUP_STATUS_CONFIG[info.status];
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
class={`inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded ${config.bgColor} ${config.color}`}
|
|
||||||
title={info.status === 'never' ? 'No backup found' : `Last backup: ${info.ageFormatted}`}
|
|
||||||
>
|
|
||||||
{info.status === 'fresh' && 'OK'}
|
|
||||||
{info.status === 'stale' && info.ageFormatted}
|
|
||||||
{info.status === 'critical' && info.ageFormatted}
|
|
||||||
{info.status === 'never' && 'Never'}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.guest.template}>
|
<Show when={props.guest.template}>
|
||||||
<span class="text-xs text-gray-400">-</span>
|
<span class="text-xs text-gray-400">-</span>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue