1) Карта выгружается (map.destroy()) через 2с после ухода в режим «График»
— освобождает тайлы/canvas/GPU (~350–450 МБ); отменяется при возврате.
Метод MapView.destroy() (сброс map/objectManager/initPromise/pending);
в onModeChange сброс mapLoadedKey, иначе refreshMap короткозамыкал render и
свежий ObjectManager оставался пустым.
2) MIN_LABEL_W 140→200 — три пилюли Город/Адрес/Код не обрезаются на узком
столбце; container-query прячет подпись «Показывать» при ширине < 255px.
3) Правый/левый отступ 7px у содержимого строки (#timeline .vis-label .vis-inner,
специфичность выше собственного padding vis-timeline) — адрес не липнет к
границе столбца.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) Первая колонка графика: в углу — кнопки «Показывать» Город/Адрес/Код
(стиль пилюль, активная — фиолетовая). Строка собирается из включённых
сущностей, разделённых кружком 7px; код без скобок; адрес тёмный, город/код
приглушённые. «Адрес» (или последний активный) нельзя снять — строка не пустеет.
Доступно всем ролям, выбор в localStorage. Лёгкое обновление подписей
(view.refreshLabels — без пересборки баров).
2) Временная шкала: единые 3-буквенные месяцы (format.minorLabels/majorLabels),
год индиго и жирнее (.vis-text.vis-major), подсветка текущего месяца
(фон-элемент cur-month-bg). Красная линия «сегодня» без изменений.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
По умолчанию плашка свёрнута (65×35) — белая плашка с фиолетовым пилюлем текущего
масштаба («12М» и т.п.). При наведении разворачивается (145×80) в полный контрол
(лейбл + слайдер + баббл) с плавной анимацией размеров (overflow:hidden + фикс.
размеры) и кросс-фейдом содержимого. Сворачивается обратно через 1с после ухода
курсора (JS-таймер в main.ts, класс .expanded; повторное наведение отменяет). В
развёрнутом виде содержимое смещено на 4px вниз для вертикальной центровки.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Поле .dp-search било по специфичности правилом .hgroup-fields input[type=text]
{width:100%} (0,2,1) — поле всегда было 100% (=padding-box со скроллом), из-за
чего рамка резалась краем и появлялась фантомная горизонтальная полоса. Селектор
усилен до .dropdown-panel input.dp-search (0,2,1, позже в источнике), ширина —
fill-available (заполняет доступную ширину минус вертикальный скролл).
- Панель: max-width 350px + overflow:auto — контент шире 350px даёт горизонтальный
скролл (убраны overflow-x:hidden и scrollbar-gutter из прошлой правки).
Задеплоено на прод без тега.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Панель подстраивает ширину под контент (width: max-content, min 220, max 96vw),
overflow-x: hidden убирает фантомную горизонтальную полосу от вертикального
скролла (метки-флексы тянулись на полную ширину под скроллбар), scrollbar-gutter:
stable резервирует место под вертикальный скролл. Задеплоено на прод без тега.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Над календарём — две пилюли-конца в стиле чипсов фильтров (accent-weak
заливка, индиго-текст, скруглённые). Активная пилюля (куда пишется следующий
клик) с индиго-рамкой; клик по дню пишет в активный конец, фокус авто-переходит
ко второму. У каждой пилюли × — очистить именно этот конец, что даёт ОТКРЫТЫЙ
диапазон: «с X» (без конца) или «по Y» (без начала). «Применить» активна, если
задан хотя бы один конец. Нативные #date-start/#date-end по-прежнему источник
истины, любой из них может быть пустым; фильтр и URL уже читают from/to по
отдельности.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Промежуточные дни диапазона были сплошной заливкой-пилюлей; теперь каждый — отдельный светлый кружок 27px, вровень с акцентными кружками концов (тоже 27px). Убраны соединяющие полу-градиенты у концов.
Задеплоено на прод поверх v0.2.15 без нового тега.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
После унификации полей до 195px «Период» больше не занимает 2 колонки — span 2 оставлял пустоту справа и криво переносил поля (у анонима «Поиск» уезжал на вторую строку). Теперь все поля — обычные колонки 195px, ряд заполняется слева направо.
Задеплоено прямо на прод поверх v0.2.15 без нового тега.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .hgroup-fields: колонки 170→195px (все фильтры одинаковой ширины)
- «Период» (.dr-field) 333→195px, вровень с остальными
- Формат даты в поле «Период» — 2-значный год (05.08.26 — 20.08.26), чтобы диапазон целиком помещался в 195px; в чипсах год остался полным
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Тогглы: Бренд+Компания в одну строку, блок выровнен ровно по полосе контролов (верх = верх полей, низ = низ «Поиска»), равномерные зазоры
- Датапикер: выбор диапазона больше не применяется автоматически по второму клику — только кнопкой «Применить» (+ «Сбросить» для незавершённого выбора)
- Степперы «Оформления»: ровно 80px по ширине
- Поле поиска в дропдаунах: равномерная рамка 1px по всем сторонам вместо разноцветных краёв
- Балун на карте: клик по пустой карте закрывает балун (guard от самозакрытия по «своему» клику, клик по другому кластеру открывает новый)
- Полоса фильтров: поля сужены на 10px, «Период» до 333px
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second modernization batch (reference: easystudy ui_elements):
- Toast/snackbar notifications (dark, green check) for actions — copy link.
- Mode switch: icons (horizontal bars / pin / columns) + a sliding active
indicator; fixed width so the bold active label never resizes the block.
- Appearance number fields became steppers: an editable field with centred −/+
buttons, ~20% narrower.
- Date filter: the two native date inputs are replaced by a custom range picker
— one field "Период (даты)" + a calendar popup with month nav and range
selection (circle endpoints, light band between). The native inputs stay hidden
as the source of truth so all filter/URL/chip wiring is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modernised several controls (reference: easystudy ui_elements catalogue):
- Display checkboxes (Бренд/Компания/Коллизии/Все поверхности) are now toggle
switches; the option checkboxes inside filter dropdowns stay normal.
- Counters became stat tiles: header "Поверхности N" (accent) and the map
legend (свободно/занято/всего + dashed "без координат").
- Filter dropdowns gained an in-panel search that filters the option list
(shown for lists >7, auto-focused on open).
- Timeline scale control restyled as a filled slider with a value bubble, moved
out of the header to float over the top-right of the chart just below the time
axis (5px gap), bubble pointing up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
Timeline: clicking a surface in the left column now flies the map to it — it
switches to the combined "Карта и график" view when only the timeline is open,
then centres+zooms on the surface's cluster (deferred focus waits for the map to
render; works for anonymous and manager users). Replaces the old manager-only
board detail panel on that click (the map card already shows photos + bookings).
Map panel: for clusters with many surfaces it now widens (tabs flow into 3-4
columns) and its height is capped to the space available in the window, so it
never overflows the browser window and scrolls inside instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set minClusterSize:1 so a solitary surface is drawn with the same donut icon
(showing "1", green when free / red when busy) instead of a small dot, and every
click flows through the single cluster handler into our panel. A cluster of one
keeps the rich single-surface hover tooltip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the Yandex balloon with our own floating card, opened on marker/cluster
click. It fits its content (no forced scroll), is draggable by the header and
resizable from the bottom-right corner, and — for clusters — carries tabs to
switch between the co-located surfaces. Photo gallery + lightbox are preserved.
On open it positions itself next to the clicked cluster and offsets toward the
free side of the map (the map half the cluster sits in picks the direction, e.g.
bottom-left cluster -> panel opens up and to the right), clamped inside the map.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Photos live on disk as photos/<board_key>/<file>.{jpg,jpeg,png} (a read-only
volume). A cached directory index (60s TTL) attaches a `photos` URL list to each
surface in /api/map; a /photos StaticFiles mount serves the files. Photos are
public — shown to anonymous visitors and managers alike.
The balloon renders a gallery (main image + thumbnail strip + "1 / N" counter);
clicking a thumbnail swaps the main image, and clicking the main image / "+N"
opens a fullscreen lightbox over the map (prev/next, Esc, backdrop close).
Surfaces without photos render exactly as before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- default colour for status "РК. Размещено" is now green #26c04c (was indigo)
- add a #26c04c circle favicon (inline SVG data URI) for the browser tab
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the bar-label overflow handling: an oversized label defaults to the
right of the bar, but if it would collide with the next bar on the same row it
is placed to the left instead (whichever side has room; larger gap wins when
neither fits). Also make the trigger a debounce (was a throttle) so the layout
runs after a zoom/redraw burst settles, once bar widths are final.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- appearance menu opens 20px below the header/chips and 58px from the right edge
- map "no coordinates" badge is manager-only (hidden for anonymous visitors)
- timeline bar label that is wider than its bar now renders just past the bar's
right edge as dark text on the track, instead of being clipped; labels that
fit still sit inside the bar in white. Applied post-render (measure overflow),
re-run on changed/rangechanged with a double rAF so bar widths are settled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Counter snippet at the top of <body> (webvisor, clickmap, trackLinks).
Lives in index.html only — no change to the JS/CSS bundle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- appearance menu drops below the active-filter chips row when it is shown,
and sits 10px further from the right edge
- "Скопировать ссылку" moved to the left of the chips, separated by a divider
- enlarge "Green Media" title so it is flush with the "Адресная программа"
line below it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Appearance ("Оформление") menu:
- add missing status "РК. Предварительное бронирование" (8 statuses total)
- strip the systemic "РК. " prefix from status names in the UI (menu,
tooltip, status filter dropdown, active-filter chips); raw values stay
intact for filtering/URLs
- fixed-position menu: opens 25px below the header, ends >=30px above the
window bottom, scrolls inside — never overflows the viewport
- drop the "Прочие статусы" row
- per-status grey reset glyph (circular arrows) restoring its default colour
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Timeline can now list the entire inventory (occupied + free + never
booked), not just booked surfaces. New manager-only "Все поверхности"
checkbox (on by default; forced on and hidden for anonymous visitors so
they can spot what is currently free with no future bookings).
/api/boards gains all_surfaces: sources rows from board_info and unions
pf_board so a just-booked surface not yet in the inventory snapshot still
gets a row. Surface facets (city/dimension/search) narrow rows; booking
facets (brand/manager/status) narrow to booked surfaces; dates are a
window that narrows bars, not the surface set.
UI polish:
- timeline tooltip flips up/left near viewport edges
- anonymous "Войти" button filled accent, inverts to white-on-accent hover
- appearance button uses a gear icon instead of "+"
- header counter shows only "Поверхности: N"
- map "всего" counter includes surfaces without coordinates
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anonymous visitors briefly saw the manager-only filters (Brand/Manager/Status),
checkboxes and appearance menu on refresh, because applyRoleUi() hides them only
after the async Keycloak check-sso resolves.
Fix: mark those elements .manager-only and hide them pre-paint via a .role-pending
class set on <html> by the inline head script, gated by a render-blocking CSS rule
(html.role-pending .manager-only { display:none }). applyRoleUi() drops
.role-pending once the role is known and then applies the final per-element state.
Default is now fail-closed (hidden until confirmed manager), so anonymous never
flashes them; managers see the controls appear a moment after load.
Two-tier access for map.intense-sale.ru:
- anonymous: truncated data (no client/brand/manager/status, single-colour
bars, dates-only tooltip), enforced server-side.
- manager: full data behind Keycloak login (realm intense-sale, role manager).
- timeline: RenderFlags.managerView drives the tooltip. Anonymous tier shows
a minimal tooltip (date range + duration only) — no title, task №, status,
manager or Planfix hint. Manager tier keeps the rich tooltip.
- header: login/logout button pinned to the top-right corner (.auth-wrap,
absolute) instead of sitting next to the view-mode switch.
Keycloak: register bare origin in post.logout.redirect.uris so keycloak-js
logout (redirectUri without trailing path) is accepted.
Backend (main.py + auth.py):
- Validate Bearer JWT against the intense-sale realm JWKS (RS256, issuer
checked, aud skipped). Missing/invalid token = anonymous, never an error,
so the public map keeps working when Keycloak is down.
- Anonymous truncation: /api/meta hides brand/manager/status facets;
/api/bookings blanks brand/company_name/manager/task_id/task_status
(bars stay, one neutral colour); /api/map keeps occupied_now but empties
booking details. Hidden facet filters are ignored for anonymous callers.
- boards stay visible to all (physical inventory: address/city/dimension).
Frontend (keycloak-js):
- Silent SSO (check-sso) — anonymous by default, Войти/Выйти button.
- Bearer token attached + refreshed on every API call.
- Anonymous UI hides Brand/Manager/Status filters, Brand/Company toggles,
appearance menu, collisions and labels; board detail panel (Planfix
deep-links) gated to managers.
Test manager: user / 1234567890 (realm role `manager`).
- Shareable filter links: current filters sync to the URL query
(?city=&brand=&dimension=&manager=&status=&q=&from=&to=, repeated params for
multi-selects) and are restored on load; a "Скопировать ссылку" action in the
chips row copies the current URL.
- Board detail panel: clicking a timeline row label opens a right-side panel with
the surface's code, city · size, address and the full list of its bookings
(brand/company, dates, status dot, manager, Planfix link). Close via ✕ /
backdrop / Esc. Bars still open the Planfix task. Dropdown gains a public
setOnBoardClick on the timeline view.
- Fix: the panel showed empty on load because .board-panel{display:flex}
overrode the UA [hidden] rule — added an explicit [hidden]{display:none}.
- Spacing: tokenised the secondary chrome (header groups, fields, dropdown panel,
appearance menu, legend, map tooltip) onto the Open Props --size-* scale;
structural density of the header/fields/inputs deliberately kept.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Active-filter chips row under the header: one chip per selected city / brand /
dimension / manager / status plus search and date bounds, each removable; a
"Сбросить всё" clears everything. Dropdown gains setSelected()/clear().
- Empty states for the timeline and map: icon + title + subtitle + "Сбросить
фильтры" button (wired to the same reset). (empty toggled to display:flex.)
- Delayed indeterminate loading bar (250ms threshold so fast loads don't flash),
covering both the data and the map fetches via an in-flight counter.
- Polish: pop-in animation for dropdown/appearance menus, :focus-visible accent
rings, :active press on buttons, thin themed scrollbars in panels, tabular
figures on the timeline date axis and tooltips. New components use the Open
Props --size-* spacing scale.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add Open Props as the token foundation (@import "open-props/style"). Our
semantic tokens (--bg/--surface/--border/--text/--shadow-*/--transition) are
now backed by its gray ramp, shadows and easing; the rest of the CSS is
untouched since it already references var(--…).
- Accent → indigo #4f46e5 (--accent + derived rings / month-hover), and the
timeline bar default colours (appearance.ts) move to the same indigo so the
content and UI share one palette.
- Font → IBM Plex Sans, self-hosted via @fontsource (latin + cyrillic, weights
400/500/600/700) so there is no runtime Google Fonts dependency. Backed into
--font-sans (overrides Open Props' :where() default). Tabular figures on the
counters and map legend.
- Collision default colour → warm amber-orange #e8590c: distinct from the indigo
bars and from the green/red map status, reads as a conflict warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Map (map.ts, styles.css):
- Single markers now use the same donut colours (islands#circleDotIcon +
iconColor #37b24d / #e24b4a) instead of Yandex's brighter presets, so single
and cluster markers match.
- Legend is a vertical stack with relabelled live counts: "свободно / занято /
всего", and the "без координат" badge hides when the count is zero.
- Cluster tooltip shows the brand for occupied surfaces (coral); legend raised
above the Yandex controls.
- Hover tooltip: smart positioning — flips above/left of the cursor when it would
overflow the viewport (bottom clusters no longer clip), max-height safety.
- Bug fix: hide the hover tooltip on marker/cluster click — it no longer sticks
and follows the cursor after a balloon opens.
- Bug fix: clusterDisableClickZoom=true so a cluster click opens its balloon at
any zoom instead of only at max zoom.
Header (index.html, styles.css):
- Scale + appearance + checkboxes recomposed into one compact block; the
"Оформление" control is a round "+" button pinned to the header's bottom-right
corner with a hover tooltip; counters relabelled "Поверхности / Брони"; the
counters plate and view-mode switch share one borderless grey fill.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Map (map.ts, styles.css, index.html):
- Cluster icons are now donut charts split green (free) / red (busy) by the
occupancy of the surfaces they contain, count in the centre. Custom
clusterIconLayout with a hit-area shape; hover/click preserved.
- gridSize 64 -> 112 so nearby clusters stop overlapping at the overview zoom.
- Live legend: "свободна: X · занята: Y · поверхностей: N", reacts to filters.
- "Без координат" is now a clickable badge opening a compact codes-only panel
(header "Без координат") so the missing-coordinate surfaces can be fixed.
- Search-to-locate: when a filter narrows to one surface the map flies in and
opens its balloon.
- Balloon restyled to match the timeline tooltip; muted map tiles (ground-pane
filter) so the markers stand out.
- Cluster hover tooltip shows the brand for occupied surfaces; legend plate
raised above the Yandex controls.
Header (index.html, styles.css, main.ts):
- Scale + appearance + checkboxes recomposed into one compact block; shorter
slider; the "Масштаб" label uses the shared field-label style again.
- "Оформление" is now a round "+" button pinned to the header's bottom-right
corner with a hover tooltip; opens the same appearance menu.
- Counters relabelled "Поверхности" / "Брони"; the counters plate and the
view-mode switch now share one borderless grey fill; mode buttons sit on a
more visible grey frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend (styles.css, index.html, main.ts):
- Design tokens: single accent/border/radius/shadow/spacing system; flat header
(dropped the boxed groups) with a subtle bottom divider.
- Unified controls: 36px height, consistent focus ring; search field gets a
magnifier icon; view-mode switch is now a compact vertical segmented control.
- Fixed the dropdown option font: `.field label` styles were leaking into the
option <label>s (they're inside .field), forcing uppercase + bold; options are
now normal weight / normal case.
- Header alignment: mode switch fills the row so its bottom lines up with the
date fields; the counters block is pinned to the same bottom line.
- Dropdown lists no longer show the column-resizer line bleeding through: the
header now establishes a stacking context (position:relative + z-index) so its
panels sit above the timeline.
- Dark theme is implemented (tokens + no-flash) but hidden for now (toggle
display:none, forced light) until it's polished — easy to re-enable.
Timeline tooltip (timeline.ts, types.ts):
- Replaced the plain multi-line text tooltip with a structured card: bold brand
title + task №, company subtitle, localized dates + duration, status as a
coloured dot, manager, a distinct collision section, and a "click to open in
Planfix" hint. Left accent stripe in the bar colour; matches the map tooltip.
- Values are escaped (escapeHtml) before going into innerHTML.
Backend (main.py):
- /api/bookings now returns `manager` (Array(String)), canonicalized to the
"Фамилия Имя" form (see canon_manager) so the tooltip can show it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/api/meta built the city/brand/dimension/manager/status option lists with five
sequential `await ch_query(...)` calls, so its latency was the sum of five
round-trips to ClickHouse. The queries are independent, and the frontend hits
/api/meta on every filter change, so this was 5x latency for no reason.
Wrap them in asyncio.gather so they fire in parallel; total time is now ~the
slowest single query plus overhead. Output verified identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both tables are ReplacingMergeTree; dedup happens only during background part
merges, not at read time. The ongoing etl_rw feed keeps inserting into pf_board,
so right after an insert that updates an existing row there is a short window
(before the forced merge, min_age_to_force_merge_seconds=15) where the same
task_id / board_key exists in two parts. A plain SELECT reads both parts and
returns the row twice.
The /api/boards query already collapses duplicates via argMax + GROUP BY, but
/api/bookings and the two /api/map queries read raw rows, so they could
intermittently double a booking (timeline/balloon) or a surface marker.
Add FINAL to those three reads (pf_board in bookings + map current, board_info
in map surfaces). At 463/515 rows the cost is negligible and it removes the
class of transient duplicates regardless of merge timing. Output verified
unchanged against the single-part baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Planfix has emitted manager names in two word orders over time ("Имя Фамилия"
from the 26 Jul 2026 n8n backfill, "Фамилия Имя" from the ongoing etl_rw feed),
so one person appeared as two distinct filter options (e.g. "Александра Столбова"
vs "Столбова Александра"), splitting their tasks across two facet values.
Fix without touching stored data:
- MANAGER_KEY_ARR: SQL expression building a word-order- and case-independent
key (lowercase, split, arraySort, rejoin) per manager array element.
- MANAGER_COND / MAP_MANAGER_COND now match on that key, so a selected manager
catches rows stored under either order.
- parse_filters normalizes incoming manager values with the same key (_mkey).
- /api/meta returns canonical "Фамилия Имя" names (CANONICAL_MANAGERS),
deduplicated; unknown names pass through unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>