rspace-online/modules/choices/components/folk-choices-dashboard.ts

127 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* <folk-choices-dashboard> — lists choice shapes (polls, rankings, spider charts)
* from the current space and links to the canvas to create/interact with them.
*/
class FolkChoicesDashboard extends HTMLElement {
private shadow: ShadowRoot;
private choices: any[] = [];
private loading = true;
private space: string;
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
this.space = this.getAttribute("space") || "demo";
}
connectedCallback() {
this.render();
this.loadChoices();
}
private getApiBase(): string {
const path = window.location.pathname;
const parts = path.split("/").filter(Boolean);
return parts.length >= 2 ? `/${parts[0]}/choices` : "/demo/choices";
}
private async loadChoices() {
this.loading = true;
this.render();
try {
const res = await fetch(`${this.getApiBase()}/api/choices`);
const data = await res.json();
this.choices = data.choices || [];
} catch (e) {
console.error("Failed to load choices:", e);
}
this.loading = false;
this.render();
}
private render() {
const typeIcons: Record<string, string> = {
"folk-choice-vote": "☑",
"folk-choice-rank": "📊",
"folk-choice-spider": "🕸",
};
const typeLabels: Record<string, string> = {
"folk-choice-vote": "Poll",
"folk-choice-rank": "Ranking",
"folk-choice-spider": "Spider Chart",
};
this.shadow.innerHTML = `
<style>
:host { display: block; padding: 1.5rem; }
.rapp-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 1rem; min-height: 36px; }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; }
.create-btns { display: flex; gap: 0.5rem; }
.create-btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.875rem; text-decoration: none; }
.create-btn:hover { border-color: #6366f1; color: #f1f5f9; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; }
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 1.25rem; cursor: pointer; text-decoration: none; display: block; }
.card:hover { border-color: #6366f1; }
.card-icon { font-size: 1.5rem; margin-bottom: 0.5rem; }
.card-title { color: #f1f5f9; font-weight: 600; font-size: 1rem; margin: 0 0 0.25rem; }
.card-type { color: #818cf8; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
.card-meta { color: #94a3b8; font-size: 0.8125rem; }
.stat { display: inline-block; margin-right: 1rem; }
.empty { text-align: center; padding: 3rem; color: #64748b; }
.empty-icon { font-size: 3rem; margin-bottom: 1rem; }
.empty p { margin: 0.5rem 0; font-size: 0.875rem; }
.loading { text-align: center; padding: 3rem; color: #94a3b8; }
.info { background: rgba(99,102,241,0.1); border: 1px solid rgba(99,102,241,0.2); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; color: #a5b4fc; font-size: 0.8125rem; }
</style>
<div class="rapp-nav">
<span class="rapp-nav__title">Choices</span>
<div class="create-btns">
<a class="create-btn" href="/${this.space}/rspace" title="Open canvas to create choices"> New on Canvas</a>
</div>
</div>
<div class="info">
Choice tools (Polls, Rankings, Spider Charts) live on the collaborative canvas.
Create them there and they'll appear here for quick access.
</div>
${this.loading ? `<div class="loading">⏳ Loading choices...</div>` :
this.choices.length === 0 ? this.renderEmpty() : this.renderGrid(typeIcons, typeLabels)}
`;
}
private renderEmpty(): string {
return `<div class="empty">
<div class="empty-icon">☑</div>
<p>No choices in this space yet.</p>
<p>Open the <a href="/${this.space}/rspace" style="color:#818cf8">canvas</a> and use the Poll, Rank, or Spider buttons to create one.</p>
</div>`;
}
private renderGrid(icons: Record<string, string>, labels: Record<string, string>): string {
return `<div class="grid">
${this.choices.map((ch) => `
<a class="card" href="/${this.space}/rspace">
<div class="card-icon">${icons[ch.type] || "☑"}</div>
<div class="card-type">${labels[ch.type] || ch.type}</div>
<h3 class="card-title">${this.esc(ch.title)}</h3>
<div class="card-meta">
<span class="stat">${ch.optionCount} options</span>
<span class="stat">${ch.voteCount} responses</span>
</div>
</a>
`).join("")}
</div>`;
}
private esc(s: string): string {
const d = document.createElement("div");
d.textContent = s;
return d.innerHTML;
}
}
customElements.define("folk-choices-dashboard", FolkChoicesDashboard);