fix: layout polish — responsive filter, scroll, resize, min cell size
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
843ac84805
commit
338ffb9c19
5 changed files with 54 additions and 10 deletions
|
|
@ -42,8 +42,8 @@
|
|||
}
|
||||
|
||||
.heatmap-wrapper .heatmap-svg-area {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.heatmap-wrapper .heatmap-filter {
|
||||
|
|
@ -56,6 +56,22 @@
|
|||
max-width: 200px;
|
||||
}
|
||||
|
||||
/* Small screens: filter above heatmap */
|
||||
@media (max-width: 1330px) {
|
||||
.heatmap-wrapper {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.heatmap-wrapper .heatmap-filter {
|
||||
padding-top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.heatmap-filter select {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
.heatmap-weekend {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
@ -67,7 +83,7 @@
|
|||
.heatmap-stats {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 8px 0 0;
|
||||
padding: 12px 0 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--tblr-secondary, #6c757d);
|
||||
flex-wrap: wrap;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -209,8 +209,9 @@ export function renderHeatmap(
|
|||
|
||||
// Compute cell size to fill available width, capped at 16px
|
||||
const containerWidth = container.clientWidth || 800;
|
||||
const maxCellSize = 18;
|
||||
const cellSize = Math.min(maxCellSize, Math.max(2, Math.floor((containerWidth - marginLeft) / numWeeks) - cellGap));
|
||||
const maxCellSize = 22;
|
||||
const minCellSize = 10;
|
||||
const cellSize = Math.min(maxCellSize, Math.max(minCellSize, Math.floor((containerWidth - marginLeft) / numWeeks) - cellGap));
|
||||
const step = cellSize + cellGap;
|
||||
const svgWidth = marginLeft + numWeeks * step;
|
||||
const svgHeight = marginTop + 7 * step + marginBottom;
|
||||
|
|
@ -297,6 +298,7 @@ export function renderHeatmap(
|
|||
if (!onCellClick) return;
|
||||
onCellClick(d.dateStr);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function init(container: HTMLElement): void {
|
||||
|
|
@ -331,6 +333,16 @@ export function init(container: HTMLElement): void {
|
|||
svgArea.className = 'heatmap-svg-area';
|
||||
wrapper.appendChild(svgArea);
|
||||
|
||||
// Shared state for current data (used by resize re-render and filter)
|
||||
let currentData: HeatmapData | null = null;
|
||||
|
||||
const doRender = (data: HeatmapData, emptyMsg?: string) => {
|
||||
currentData = data;
|
||||
renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, emptyMsg, weekStart);
|
||||
renderStats(container, data.days);
|
||||
svgArea.scrollLeft = svgArea.scrollWidth;
|
||||
};
|
||||
|
||||
// Build filter dropdown (only if projects exist)
|
||||
if (projects.length > 0) {
|
||||
const filterDiv = document.createElement('div');
|
||||
|
|
@ -363,8 +375,7 @@ export function init(container: HTMLElement): void {
|
|||
return res.json() as Promise<HeatmapData>;
|
||||
})
|
||||
.then(data => {
|
||||
renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, 'No tracking data for this project', weekStart);
|
||||
renderStats(wrapper, data.days);
|
||||
doRender(data, 'No tracking data for this project');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('KimaiHeatmap: failed to load filtered data', err);
|
||||
|
|
@ -377,6 +388,15 @@ export function init(container: HTMLElement): void {
|
|||
|
||||
container.appendChild(wrapper);
|
||||
|
||||
// Re-render on window resize (debounced)
|
||||
let resizeTimer: ReturnType<typeof setTimeout>;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => {
|
||||
if (currentData) doRender(currentData);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// Initial data fetch
|
||||
fetch(baseUrl)
|
||||
.then(res => {
|
||||
|
|
@ -384,8 +404,7 @@ export function init(container: HTMLElement): void {
|
|||
return res.json() as Promise<HeatmapData>;
|
||||
})
|
||||
.then(data => {
|
||||
renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, undefined, weekStart);
|
||||
renderStats(wrapper, data.days);
|
||||
doRender(data);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('KimaiHeatmap: failed to load data', err);
|
||||
|
|
|
|||
8
assets/test/setup.ts
Normal file
8
assets/test/setup.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// jsdom doesn't provide ResizeObserver
|
||||
if (typeof globalThis.ResizeObserver === 'undefined') {
|
||||
globalThis.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
} as unknown as typeof globalThis.ResizeObserver;
|
||||
}
|
||||
|
|
@ -4,5 +4,6 @@ export default defineConfig({
|
|||
test: {
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
setupFiles: ['./assets/test/setup.ts'],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue