Merge branch 'dev'
CI/CD / deploy (push) Successful in 5m41s
Details
CI/CD / deploy (push) Successful in 5m41s
Details
This commit is contained in:
commit
ad218e72ad
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Dashboard summary API — single aggregation endpoint for dashboard widgets.
|
||||
*
|
||||
* GET /dashboard-summary/:space → { tasks, calendar, flows }
|
||||
*
|
||||
* Uses existing MI data functions (zero new DB queries).
|
||||
*/
|
||||
|
||||
import { Hono } from "hono";
|
||||
import { getRecentTasksForMI } from "../modules/rtasks/mod";
|
||||
import { getUpcomingEventsForMI } from "../modules/rcal/mod";
|
||||
import { getRecentFlowsForMI } from "../modules/rflows/mod";
|
||||
|
||||
const dashboardRoutes = new Hono();
|
||||
|
||||
dashboardRoutes.get("/dashboard-summary/:space", (c) => {
|
||||
const space = c.req.param("space");
|
||||
if (!space) return c.json({ error: "space required" }, 400);
|
||||
|
||||
const tasks = getRecentTasksForMI(space, 5);
|
||||
const calendar = getUpcomingEventsForMI(space, 14, 5);
|
||||
const flows = getRecentFlowsForMI(space, 3);
|
||||
|
||||
return c.json({ tasks, calendar, flows });
|
||||
});
|
||||
|
||||
export { dashboardRoutes };
|
||||
|
|
@ -526,6 +526,10 @@ app.route("/api/mi", miRoutes);
|
|||
app.route("/rtasks/check", checklistCheckRoutes);
|
||||
app.route("/api/rtasks", checklistApiRoutes);
|
||||
|
||||
// ── Dashboard summary API ──
|
||||
import { dashboardRoutes } from "./dashboard-routes";
|
||||
app.route("/api", dashboardRoutes);
|
||||
|
||||
// ── Bug Report API ──
|
||||
app.route("/api/bug-report", bugReportRouter);
|
||||
|
||||
|
|
|
|||
|
|
@ -1164,6 +1164,12 @@ export function renderShell(opts: ShellOptions): string {
|
|||
tabBar.addEventListener('layer-switch', (e) => {
|
||||
const { layerId, moduleId } = e.detail;
|
||||
currentModuleId = moduleId;
|
||||
// Hide dashboard overlay if it was showing
|
||||
const dashOnSwitch = document.querySelector('rstack-user-dashboard');
|
||||
if (dashOnSwitch && dashOnSwitch.style.display !== 'none') {
|
||||
dashOnSwitch.style.display = 'none';
|
||||
tabBar.setAttribute('home-active', 'false');
|
||||
}
|
||||
saveTabs();
|
||||
if (tabCache) {
|
||||
const switchId = moduleId; // capture for staleness check
|
||||
|
|
@ -1322,6 +1328,7 @@ export function renderShell(opts: ShellOptions): string {
|
|||
const { moduleId: targetModule, spaceSlug: targetSpace } = e.detail;
|
||||
const dashboard = document.querySelector('rstack-user-dashboard');
|
||||
if (dashboard) dashboard.style.display = 'none';
|
||||
tabBar.setAttribute('home-active', 'false');
|
||||
|
||||
// If navigating to a different space, do a full navigation
|
||||
if (targetSpace && targetSpace !== spaceSlug) {
|
||||
|
|
@ -1348,6 +1355,34 @@ export function renderShell(opts: ShellOptions): string {
|
|||
}
|
||||
});
|
||||
|
||||
// ── Home button: toggle dashboard overlay when tabs are open ──
|
||||
tabBar.addEventListener('home-click', () => {
|
||||
const dashboard = document.querySelector('rstack-user-dashboard');
|
||||
if (!dashboard) return;
|
||||
|
||||
// If no tabs, dashboard is already visible — no-op
|
||||
if (layers.length === 0) return;
|
||||
|
||||
const isVisible = dashboard.style.display !== 'none';
|
||||
if (isVisible) {
|
||||
// Hide dashboard overlay, return to active tab
|
||||
dashboard.style.display = 'none';
|
||||
tabBar.setAttribute('home-active', 'false');
|
||||
// Restore active tab pane
|
||||
if (tabCache && currentModuleId) {
|
||||
tabCache.switchTo(currentModuleId);
|
||||
}
|
||||
} else {
|
||||
// Show dashboard as overlay, hide active pane
|
||||
if (tabCache) tabCache.hideAllPanes();
|
||||
dashboard.style.display = '';
|
||||
if (dashboard.refresh) dashboard.refresh();
|
||||
tabBar.setAttribute('home-active', 'true');
|
||||
var dashUrl = window.location.hostname.endsWith('.rspace.online') ? '/' : '/' + spaceSlug;
|
||||
history.pushState({ dashboard: true, spaceSlug: spaceSlug }, '', dashUrl);
|
||||
}
|
||||
});
|
||||
|
||||
tabBar.addEventListener('view-toggle', (e) => {
|
||||
const { mode } = e.detail;
|
||||
document.dispatchEvent(new CustomEvent('layer-view-mode', { detail: { mode } }));
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export class RStackTabBar extends HTMLElement {
|
|||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["active", "space", "view-mode"];
|
||||
return ["active", "space", "view-mode", "home-active"];
|
||||
}
|
||||
|
||||
get active(): string {
|
||||
|
|
@ -415,15 +415,14 @@ export class RStackTabBar extends HTMLElement {
|
|||
<style>${STYLES}</style>
|
||||
<div class="tab-bar" data-view="${this.#viewMode}">
|
||||
<div class="tabs-scroll">
|
||||
${this.#layers.length === 0
|
||||
? `<div class="tab active tab--dashboard">
|
||||
<span class="tab-indicator" style="background:#5eead4"></span>
|
||||
<span class="tab-badge" style="background:#5eead4">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
|
||||
</span>
|
||||
<span class="tab-label">Dashboard</span>
|
||||
</div>`
|
||||
: this.#layers.map(l => this.#renderTab(l, active)).join("")}
|
||||
<button class="tab-home ${!active || this.getAttribute('home-active') === 'true' ? 'tab-home--active' : ''}"
|
||||
id="home-btn" title="Dashboard">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||||
</svg>
|
||||
<span class="tab-home__label">Home</span>
|
||||
</button>
|
||||
${this.#layers.map(l => this.#renderTab(l, active)).join("")}
|
||||
</div>
|
||||
<div class="tab-add-wrap">
|
||||
<button class="tab-add" id="add-btn" title="Add layer">+</button>
|
||||
|
|
@ -942,6 +941,11 @@ export class RStackTabBar extends HTMLElement {
|
|||
// Clean up previous document-level listeners to prevent leak
|
||||
if (this.#docCleanup) { this.#docCleanup(); this.#docCleanup = null; }
|
||||
|
||||
// Home button — always present, fires home-click event
|
||||
this.#shadow.getElementById("home-btn")?.addEventListener("click", () => {
|
||||
this.dispatchEvent(new CustomEvent("home-click", { bubbles: true }));
|
||||
});
|
||||
|
||||
// Tab clicks — dispatch event but do NOT set active yet.
|
||||
// The shell's event handler calls switchTo() and sets active only after success.
|
||||
this.#shadow.querySelectorAll<HTMLElement>(".tab").forEach(tab => {
|
||||
|
|
@ -1535,6 +1539,38 @@ const STYLES = `
|
|||
display: block;
|
||||
}
|
||||
|
||||
/* ── Persistent home button ── */
|
||||
.tab-home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
padding: 4px 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--rs-text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
flex-shrink: 0;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
line-height: 1;
|
||||
}
|
||||
.tab-home:hover {
|
||||
color: var(--rs-text-primary);
|
||||
background: rgba(255,255,255,0.06);
|
||||
}
|
||||
.tab-home--active {
|
||||
color: #14b8a6;
|
||||
background: rgba(20,184,166,0.1);
|
||||
}
|
||||
.tab-home__label {
|
||||
font-size: 0.55rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Active indicator line at bottom */
|
||||
.tab-indicator {
|
||||
position: absolute;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -94,9 +94,10 @@ export class TabCache {
|
|||
return;
|
||||
}
|
||||
|
||||
// If returning from dashboard, hide it
|
||||
// If returning from dashboard, hide it and clear home-active
|
||||
const dashboard = document.querySelector("rstack-user-dashboard");
|
||||
if (dashboard) (dashboard as HTMLElement).style.display = "none";
|
||||
document.querySelector("rstack-tab-bar")?.setAttribute("home-active", "false");
|
||||
const key = this.paneKey(stateSpace, state.moduleId);
|
||||
if (this.panes.has(key)) {
|
||||
if (stateSpace !== this.spaceSlug) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue