auth ui: anon tooltip = dates only; move login button to header top-right
- 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.
This commit is contained in:
parent
b6b9c27cb0
commit
1eda6c1e9d
@ -33,7 +33,6 @@
|
||||
<button type="button" class="mode-btn" id="mode-split">Карта и график</button>
|
||||
</div>
|
||||
<button type="button" class="theme-toggle" id="theme-toggle" title="Переключить тему" aria-label="Переключить тему"></button>
|
||||
<button type="button" class="auth-btn" id="auth-btn" style="display:none">Войти</button>
|
||||
</div>
|
||||
|
||||
<!-- Group 2: fields (4-column grid; dates + search on the 2nd row) -->
|
||||
@ -123,6 +122,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Login / logout button, pinned to the header's top-right corner -->
|
||||
<div class="auth-wrap">
|
||||
<button type="button" class="auth-btn" id="auth-btn" style="display:none">Войти</button>
|
||||
</div>
|
||||
|
||||
<!-- Appearance ("+") button, pinned to the header's bottom-right corner -->
|
||||
<div class="decor-wrap">
|
||||
<button type="button" class="decor-btn" id="decor-btn" aria-label="Оформление" data-tip="Оформление">+</button>
|
||||
|
||||
@ -130,12 +130,13 @@ function flags(): RenderFlags {
|
||||
// Anonymous tier: no labels (data is blanked anyway) and no collision
|
||||
// highlighting, so every bar renders in the single neutral status colour.
|
||||
if (!isManager()) {
|
||||
return { withBrand: false, withCompany: false, withCollisions: false };
|
||||
return { withBrand: false, withCompany: false, withCollisions: false, managerView: false };
|
||||
}
|
||||
return {
|
||||
withBrand: el.showBrand.checked,
|
||||
withCompany: el.showCompany.checked,
|
||||
withCollisions: el.showCollisions.checked,
|
||||
managerView: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -313,6 +313,9 @@ input::placeholder { color: var(--text-muted); }
|
||||
}
|
||||
.theme-toggle:hover { border-color: var(--border-strong); background: var(--surface); }
|
||||
|
||||
/* Login / logout button — pinned to the header's top-right corner */
|
||||
.auth-wrap { position: absolute; right: 18px; top: 12px; z-index: 3; }
|
||||
|
||||
/* Login / logout button (mirrors the toggle styling, sized to its text label) */
|
||||
.auth-btn {
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg);
|
||||
|
||||
@ -11,6 +11,9 @@ export interface RenderFlags {
|
||||
withBrand: boolean;
|
||||
withCompany: boolean;
|
||||
withCollisions: boolean;
|
||||
// Manager tier: show the rich tooltip (title, task №, status, manager, Planfix
|
||||
// hint). Anonymous tier gets a minimal tooltip with only the date range.
|
||||
managerView: boolean;
|
||||
}
|
||||
|
||||
export interface TimelineElements {
|
||||
@ -81,7 +84,7 @@ export function createTimelineView(el: TimelineElements, appearance: Appearance)
|
||||
const itemsDS = new DataSet<any>();
|
||||
let lastBoards: Board[] = [];
|
||||
let lastBookings: Booking[] = [];
|
||||
let lastFlags: RenderFlags = { withBrand: true, withCompany: false, withCollisions: true };
|
||||
let lastFlags: RenderFlags = { withBrand: true, withCompany: false, withCollisions: true, managerView: true };
|
||||
let onBoardClick: ((boardId: string) => void) | null = null;
|
||||
|
||||
const options: TimelineOptions = {
|
||||
@ -315,32 +318,39 @@ export function createTimelineView(el: TimelineElements, appearance: Appearance)
|
||||
const statusColor = colorForStatus(appearance, bk.task_status);
|
||||
const color = isCollision ? appearance.collisionColor : statusColor;
|
||||
|
||||
// ---- rich hover tooltip (structured HTML; values escaped) ----
|
||||
// ---- hover tooltip (structured HTML; values escaped) ----
|
||||
const esc = escapeHtml;
|
||||
const title = brand || company || 'Без названия';
|
||||
const managers = (bk.manager || []).filter(Boolean).join(', ');
|
||||
const dur = durationLabel(bk.start_date, bk.end_date);
|
||||
const tt: string[] = [];
|
||||
tt.push('<div class="tt-head">');
|
||||
tt.push(`<span class="tt-title">${esc(title)}</span>`);
|
||||
if (bk.task_id) tt.push(`<span class="tt-id">№${esc(bk.task_id)}</span>`);
|
||||
tt.push('</div>');
|
||||
if (brand && company) tt.push(`<div class="tt-sub">${esc(company)}</div>`);
|
||||
tt.push('<div class="tt-sep"></div>');
|
||||
tt.push(`<div class="tt-row"><span class="tt-ico">📅</span><span>${fmtDate(bk.start_date)} – ${fmtDate(bk.end_date)}${dur ? ' · ' + esc(dur) : ''}</span></div>`);
|
||||
if (bk.task_status) tt.push(`<div class="tt-row"><span class="tt-dot" style="background:${statusColor}"></span><span>${esc(bk.task_status)}</span></div>`);
|
||||
if (managers) tt.push(`<div class="tt-row"><span class="tt-ico">👤</span><span>${esc(managers)}</span></div>`);
|
||||
if (isCollision) {
|
||||
tt.push('<div class="tt-warn"><div class="tt-warn-head">⚠ Коллизия</div>');
|
||||
for (const pIdx of partnerIdxs!) {
|
||||
const p = bookings[pIdx]!;
|
||||
const pLabel = p.brand || p.company_name || 'Без названия';
|
||||
tt.push(`<div class="tt-warn-row">${esc(pLabel)} <span class="tt-warn-dates">${fmtDate(p.start_date)} – ${fmtDate(p.end_date)}</span></div>`);
|
||||
}
|
||||
const datesRow = `<div class="tt-row"><span class="tt-ico">📅</span><span>${fmtDate(bk.start_date)} – ${fmtDate(bk.end_date)}${dur ? ' · ' + esc(dur) : ''}</span></div>`;
|
||||
let tooltipHtml: string;
|
||||
if (!flags.managerView) {
|
||||
// Anonymous tier: date range only — no title, task №, status or Planfix hint.
|
||||
tooltipHtml = `<div class="tt" style="--tt-accent:${color}">${datesRow}</div>`;
|
||||
} else {
|
||||
const title = brand || company || 'Без названия';
|
||||
const managers = (bk.manager || []).filter(Boolean).join(', ');
|
||||
const tt: string[] = [];
|
||||
tt.push('<div class="tt-head">');
|
||||
tt.push(`<span class="tt-title">${esc(title)}</span>`);
|
||||
if (bk.task_id) tt.push(`<span class="tt-id">№${esc(bk.task_id)}</span>`);
|
||||
tt.push('</div>');
|
||||
if (brand && company) tt.push(`<div class="tt-sub">${esc(company)}</div>`);
|
||||
tt.push('<div class="tt-sep"></div>');
|
||||
tt.push(datesRow);
|
||||
if (bk.task_status) tt.push(`<div class="tt-row"><span class="tt-dot" style="background:${statusColor}"></span><span>${esc(bk.task_status)}</span></div>`);
|
||||
if (managers) tt.push(`<div class="tt-row"><span class="tt-ico">👤</span><span>${esc(managers)}</span></div>`);
|
||||
if (isCollision) {
|
||||
tt.push('<div class="tt-warn"><div class="tt-warn-head">⚠ Коллизия</div>');
|
||||
for (const pIdx of partnerIdxs!) {
|
||||
const p = bookings[pIdx]!;
|
||||
const pLabel = p.brand || p.company_name || 'Без названия';
|
||||
tt.push(`<div class="tt-warn-row">${esc(pLabel)} <span class="tt-warn-dates">${fmtDate(p.start_date)} – ${fmtDate(p.end_date)}</span></div>`);
|
||||
}
|
||||
tt.push('</div>');
|
||||
}
|
||||
tt.push('<div class="tt-hint">↗ Клик — открыть в ПланФикс</div>');
|
||||
tooltipHtml = `<div class="tt" style="--tt-accent:${color}">${tt.join('')}</div>`;
|
||||
}
|
||||
tt.push('<div class="tt-hint">↗ Клик — открыть в ПланФикс</div>');
|
||||
const tooltipHtml = `<div class="tt" style="--tt-accent:${color}">${tt.join('')}</div>`;
|
||||
|
||||
return {
|
||||
id: bk.board_id + '__' + idx,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user