mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-16 12:12:15 +01:00
add tooltips to idlestatus indicators
This commit is contained in:
@@ -521,7 +521,7 @@ onActivated(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fullcalendar :deep(.fc-event) {
|
.fullcalendar :deep(.fc-event) {
|
||||||
border-radius: var(--radius);
|
border-radius: calc(var(--radius) - 4px);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createPlugin, PluginDef } from '@fullcalendar/core';
|
import { createPlugin, PluginDef } from '@fullcalendar/core';
|
||||||
|
import { computePosition, flip, shift, offset } from '@floating-ui/dom';
|
||||||
|
|
||||||
export interface ActivityPeriod {
|
export interface ActivityPeriod {
|
||||||
start: string;
|
start: string;
|
||||||
@@ -17,6 +18,52 @@ declare module '@fullcalendar/core' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and manages a tooltip element for activity status boxes
|
||||||
|
*/
|
||||||
|
function createTooltip(): HTMLElement {
|
||||||
|
const tooltip = document.createElement('div');
|
||||||
|
tooltip.className =
|
||||||
|
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground';
|
||||||
|
tooltip.style.position = 'fixed';
|
||||||
|
tooltip.style.pointerEvents = 'none';
|
||||||
|
tooltip.style.opacity = '0';
|
||||||
|
tooltip.style.whiteSpace = 'nowrap';
|
||||||
|
tooltip.style.transform = 'scale(0.95)';
|
||||||
|
tooltip.style.transition = 'opacity 150ms, transform 150ms';
|
||||||
|
document.body.appendChild(tooltip);
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows tooltip for an activity status box
|
||||||
|
*/
|
||||||
|
function showTooltip(box: HTMLElement, tooltip: HTMLElement, text: string) {
|
||||||
|
tooltip.textContent = text;
|
||||||
|
tooltip.style.opacity = '1';
|
||||||
|
tooltip.style.transform = 'scale(1)';
|
||||||
|
|
||||||
|
const updatePosition = () => {
|
||||||
|
computePosition(box, tooltip, {
|
||||||
|
placement: 'right',
|
||||||
|
middleware: [offset(8), flip(), shift({ padding: 5 })],
|
||||||
|
}).then(({ x, y }) => {
|
||||||
|
tooltip.style.left = `${x}px`;
|
||||||
|
tooltip.style.top = `${y}px`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the tooltip
|
||||||
|
*/
|
||||||
|
function hideTooltip(tooltip: HTMLElement) {
|
||||||
|
tooltip.style.opacity = '0';
|
||||||
|
tooltip.style.transform = 'scale(0.95)';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders activity status boxes in the calendar time grid
|
* Renders activity status boxes in the calendar time grid
|
||||||
*/
|
*/
|
||||||
@@ -30,6 +77,10 @@ export function renderActivityStatusBoxes(
|
|||||||
const existingBoxes = calendarEl.querySelectorAll('.activity-status-box');
|
const existingBoxes = calendarEl.querySelectorAll('.activity-status-box');
|
||||||
existingBoxes.forEach((box) => box.remove());
|
existingBoxes.forEach((box) => box.remove());
|
||||||
|
|
||||||
|
// Clean up existing tooltips
|
||||||
|
const existingTooltips = document.querySelectorAll('.activity-status-tooltip');
|
||||||
|
existingTooltips.forEach((tooltip) => tooltip.remove());
|
||||||
|
|
||||||
// Remove has-activity-status class from all lanes
|
// Remove has-activity-status class from all lanes
|
||||||
const allLanes = calendarEl.querySelectorAll('.fc-timegrid-col');
|
const allLanes = calendarEl.querySelectorAll('.fc-timegrid-col');
|
||||||
allLanes.forEach((lane) => lane.classList.remove('has-activity-status'));
|
allLanes.forEach((lane) => lane.classList.remove('has-activity-status'));
|
||||||
@@ -53,6 +104,9 @@ export function renderActivityStatusBoxes(
|
|||||||
activityPeriods.length
|
activityPeriods.length
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create a single tooltip instance to be reused
|
||||||
|
const tooltip = createTooltip();
|
||||||
|
|
||||||
// Get the calendar's current view to determine dates
|
// Get the calendar's current view to determine dates
|
||||||
const dateHeaders = calendarEl.querySelectorAll('.fc-col-header-cell');
|
const dateHeaders = calendarEl.querySelectorAll('.fc-col-header-cell');
|
||||||
|
|
||||||
@@ -115,7 +169,31 @@ export function renderActivityStatusBoxes(
|
|||||||
box.style.left = '4px';
|
box.style.left = '4px';
|
||||||
box.style.right = '4px';
|
box.style.right = '4px';
|
||||||
box.style.zIndex = '10';
|
box.style.zIndex = '10';
|
||||||
box.style.borderRadius = '4px';
|
box.style.cursor = 'default';
|
||||||
|
|
||||||
|
// Calculate duration in minutes
|
||||||
|
const actualStart = periodStart > laneDateStart ? periodStart : laneDateStart;
|
||||||
|
const actualEnd = periodEnd < laneDateEnd ? periodEnd : laneDateEnd;
|
||||||
|
const durationMs = actualEnd.getTime() - actualStart.getTime();
|
||||||
|
const durationMinutes = Math.round(durationMs / 60000);
|
||||||
|
|
||||||
|
// Format duration
|
||||||
|
const hours = Math.floor(durationMinutes / 60);
|
||||||
|
const minutes = durationMinutes % 60;
|
||||||
|
const durationText = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||||
|
|
||||||
|
// Add tooltip text based on status
|
||||||
|
const status = period.isIdle ? 'Idling' : 'Active';
|
||||||
|
const tooltipText = `${status} (${durationText})`;
|
||||||
|
|
||||||
|
// Add hover event listeners for tooltip
|
||||||
|
box.addEventListener('mouseenter', () => {
|
||||||
|
showTooltip(box, tooltip, tooltipText);
|
||||||
|
});
|
||||||
|
|
||||||
|
box.addEventListener('mouseleave', () => {
|
||||||
|
hideTooltip(tooltip);
|
||||||
|
});
|
||||||
|
|
||||||
// Position relative to the lane
|
// Position relative to the lane
|
||||||
const laneFrame = lane.querySelector('.fc-timegrid-col-frame');
|
const laneFrame = lane.querySelector('.fc-timegrid-col-frame');
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function onSelectChange(checked: boolean) {
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
@update:checked="onSelectChange" />
|
@update:checked="onSelectChange" />
|
||||||
<div class="flex items-center min-w-0 space-x-1 @lg:space-x-2">
|
<div class="flex items-center min-w-0">
|
||||||
<GroupedItemsCountButton :expanded="expanded" @click="expanded = !expanded">
|
<GroupedItemsCountButton :expanded="expanded" @click="expanded = !expanded">
|
||||||
{{ timeEntry?.timeEntries?.length }}
|
{{ timeEntry?.timeEntries?.length }}
|
||||||
</GroupedItemsCountButton>
|
</GroupedItemsCountButton>
|
||||||
|
|||||||
Reference in New Issue
Block a user