ui: fresh panel per cluster click, 5-step scale, unified column resizer (v0.2.11)

- Map panel: every marker/cluster click reopens a fresh panel repositioned next
  to the just-clicked cluster (with a pop-in), instead of leaving it in place and
  only swapping the content.
- Timeline scale slider: 5 fixed stops — 6/12/18/24/30 months; label is now
  "Масштаб графика: <6М|12М|18М|24М|30М>" (default 12М).
- Address-column resizer now matches the timeline/map splitter: a 6px solid bar
  that turns accent on hover/drag, so both dividers look identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
aaverbitskiy 2026-08-15 13:40:10 +00:00
parent f4c1de70b4
commit d7c1ce7b77
6 changed files with 29 additions and 31 deletions

View File

@ -123,9 +123,9 @@
<!-- Group 3: scale + appearance + checkboxes (compact stack) -->
<div class="hgroup hgroup-controls">
<div class="field zoom-field">
<label for="zoom-slider">Масштаб: <span id="zoom-label">12 месяцев</span></label>
<label for="zoom-slider">Масштаб графика: <span id="zoom-label">12М</span></label>
<div class="zoom-row">
<input type="range" id="zoom-slider" min="0" max="9" step="1" value="3" />
<input type="range" id="zoom-slider" min="0" max="4" step="1" value="1" />
</div>
</div>
<div class="checks manager-only">

View File

@ -1,7 +1,7 @@
{
"name": "mapdash-frontend",
"private": true,
"version": "0.2.10",
"version": "0.2.11",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -454,7 +454,7 @@ async function init(): Promise<void> {
rebuildDecorMenu();
view.applyLayout();
await loadData(true);
const defaultZoomIdx = 3;
const defaultZoomIdx = 1; // 12М
el.zoomSlider.value = String(defaultZoomIdx);
el.zoomLabel.textContent = zoomSteps[defaultZoomIdx]!.label;
view.setScale(zoomSteps[defaultZoomIdx]!.days);

View File

@ -301,22 +301,22 @@ function ensureMapPanel(): Panel {
function showMapPanel(surfaces: MapSurface[], idx: number, place: PanelPlace): void {
const p = ensureMapPanel();
const firstOpen = p.el.style.display === 'none';
p.surfaces = surfaces;
if (firstOpen) {
// Fresh open: reset size. Widen when there are many surfaces so the tab list
// flows into more columns (a shorter panel, less scrolling).
p.el.style.height = '';
p.el.style.maxHeight = '';
const n = surfaces.length;
const cols = n <= 8 ? 2 : n <= 24 ? 3 : 4;
p.el.style.width = cols > 2 ? Math.min(320 + (cols - 2) * 150, Math.round(window.innerWidth * 0.92)) + 'px' : '';
}
// Every click is a fresh panel: reset size, widen when there are many surfaces
// (tabs flow into more columns → shorter panel), then reposition next to the
// just-clicked cluster. A new click never reuses the previous position.
p.el.style.height = '';
p.el.style.maxHeight = '';
const n = surfaces.length;
const cols = n <= 8 ? 2 : n <= 24 ? 3 : 4;
p.el.style.width = cols > 2 ? Math.min(320 + (cols - 2) * 150, Math.round(window.innerWidth * 0.92)) + 'px' : '';
p.el.style.display = '';
renderPanel(idx);
// Position only on a fresh open (measure the now-rendered size first, so we can
// offset it toward the free side); keep position/size while it stays open.
if (firstOpen) positionPanel(p.el, place);
positionPanel(p.el, place); // measures the rendered size, then offsets to the free side
// Restart the pop-in animation so each click reads as a new panel appearing.
p.el.style.animation = 'none';
void p.el.offsetHeight;
p.el.style.animation = '';
}
function closeMapPanel(): void {

View File

@ -460,6 +460,7 @@ html.role-pending .manager-only { display: none !important; }
background: var(--bg); border: 1px solid var(--border);
border-radius: var(--radius); box-shadow: var(--shadow-md);
font-size: 13px; color: var(--text);
animation: pop-in .13s var(--ease-3);
}
.mpanel-head {
position: sticky; top: 0; z-index: 1; background: var(--bg);
@ -571,12 +572,14 @@ html.role-pending .manager-only { display: none !important; }
.vis-current-time { background-color: var(--today); }
/* Column resize handle */
/* Matches #split-resizer: a 6px solid bar that turns accent on hover/drag, so
both dividers look identical. */
#col-resizer {
position: absolute; top: 0; bottom: 0; width: 9px; margin-left: -4px;
cursor: col-resize; z-index: 25; display: none;
position: absolute; top: 0; bottom: 0; width: 6px; margin-left: -3px;
background: var(--border); cursor: col-resize; z-index: 25; display: none;
transition: background var(--transition);
}
#col-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; left: 4px; width: 1px; background: var(--border); }
#col-resizer:hover::after, #col-resizer.dragging::after { background: var(--accent); width: 2px; left: 3px; }
#col-resizer:hover, #col-resizer.dragging { background: var(--accent); }
/* Hovered-month column highlight */
.vis-item.vis-background.month-hover-bg { background-color: var(--month-hover); }

View File

@ -28,16 +28,11 @@ export interface TimelineElements {
const PLANFIX_TASK_URL = 'https://green-media.planfix.ru/task/';
export const zoomSteps: ReadonlyArray<{ days: number; label: string }> = [
{ days: 91, label: '3 месяца' },
{ days: 183, label: '6 месяцев' },
{ days: 274, label: '9 месяцев' },
{ days: 365, label: '12 месяцев' },
{ days: 457, label: '15 месяцев' },
{ days: 548, label: '18 месяцев' },
{ days: 639, label: '21 месяц' },
{ days: 731, label: '24 месяца' },
{ days: 822, label: '27 месяцев' },
{ days: 913, label: '30 месяцев' },
{ days: 183, label: '6М' },
{ days: 365, label: '12М' },
{ days: 548, label: '18М' },
{ days: 731, label: '24М' },
{ days: 913, label: '30М' },
];
const MONTH_BG_ID = '__month_hover_bg';