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

127 lines
4.6 KiB
TypeScript

/**
* <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": "\u2611",
"folk-choice-rank": "\uD83D\uDCCA",
"folk-choice-spider": "\uD83D\uDD78",
};
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; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
.header h2 { margin: 0; color: #f1f5f9; font-size: 1.5rem; }
.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="header">
<h2>\u2611 Choices</h2>
<div class="create-btns">
<a class="create-btn" href="/${this.space}/canvas" title="Open canvas to create choices">\u2795 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">\u23F3 Loading choices...</div>` :
this.choices.length === 0 ? this.renderEmpty() : this.renderGrid(typeIcons, typeLabels)}
`;
}
private renderEmpty(): string {
return `<div class="empty">
<div class="empty-icon">\u2611</div>
<p>No choices in this space yet.</p>
<p>Open the <a href="/${this.space}/canvas" 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}/canvas">
<div class="card-icon">${icons[ch.type] || "\u2611"}</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);