feat(sessions): include active event group sessions in unified session table and mobile deck without removing event deck

This commit is contained in:
Tyler Gillispie 2026-08-27 22:47:18 -07:00
parent 2fa8222062
commit 77a82cbe84
5 changed files with 416 additions and 191 deletions

View File

@ -316,7 +316,7 @@ async function handleCreateEvent(e) {
} finally { } finally {
if (btn) { if (btn) {
btn.disabled = false; btn.disabled = false;
btn.textContent = "🎟️ Create Event"; btn.textContent = "Create Event";
} }
} }
} }

View File

@ -186,9 +186,9 @@ export const WorkshopPassDrawerFragment = ({
type="submit" type="submit"
id="submitEventBtn" id="submitEventBtn"
class="btn-primary" class="btn-primary"
style="min-height: 42px; background: var(--success); border-color: var(--success);" style="min-height: 42px;"
> >
🎟 Create Event Create Event
</button> </button>
<button <button
type="button" type="button"

View File

@ -1,16 +1,23 @@
export const SessionDeckFragment = ({ export const SessionDeckFragment = ({
sessions, sessions,
currentSessionId, currentSessionId,
eventPasses = [],
}: { }: {
sessions: any[]; sessions: any[];
currentSessionId: string; currentSessionId: string;
eventPasses?: any[];
}) => { }) => {
const activeEvents = (eventPasses || []).filter(
(e) => !e.expires_at || new Date(e.expires_at).getTime() > Date.now(),
);
const hasItems = sessions.length > 0 || activeEvents.length > 0;
return ( return (
<div <div
class="mobile-only" class="mobile-only"
style="display: flex; flex-direction: column; gap: 1rem;" style="display: flex; flex-direction: column; gap: 1rem;"
> >
{sessions.length === 0 {!hasItems
? ( ? (
<div class="card" style="text-align: center; padding: 2rem;"> <div class="card" style="text-align: center; padding: 2rem;">
<p style="color: var(--text-muted); margin: 0;"> <p style="color: var(--text-muted); margin: 0;">
@ -19,7 +26,105 @@ export const SessionDeckFragment = ({
</div> </div>
) )
: ( : (
sessions.map((session) => { <>
{/* Event Group Sessions */}
{activeEvents.map((event) => {
const claimed = Number(event.seats_claimed) || 0;
const maxSeats = Number(event.max_seats) || 0;
const capacityStr = maxSeats > 0
? `${claimed}/${maxSeats}`
: `${claimed}/∞`;
return (
<div
class="card"
key={`event-${event.id}`}
style="margin-bottom: 0; border-left: 4px solid var(--primary);"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.65rem;">
<div style="display: flex; align-items: center; justify-content: center; width: 38px; height: 38px; background: var(--surface-muted); border-radius: var(--radius-md); font-size: 1.2rem;">
🎟
</div>
<div>
<div style="font-weight: 700; font-size: 0.95rem; color: var(--text-primary);">
{event.name}
</div>
<div style="font-size: 0.75rem; color: var(--text-muted); font-family: monospace;">
PIN: {event.pin_code || "---"} · {event.slug}
</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span class="badge badge-info">Event</span>
<button
type="button"
class="btn-danger end-event-btn"
data-event-id={event.id}
data-event-name={event.name}
style="padding: 0.15rem 0.5rem; font-size: 0.75rem; min-height: 24px; border-radius: var(--radius-sm);"
>
🗑 Revoke
</button>
</div>
</div>
<div style="font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 1rem; line-height: 1.6;">
<div style="margin-bottom: 0.25rem;">
<strong>Scope & Seats:</strong> {event.app_name
? `${event.app_name} (${event.role || "viewer"})`
: (event.role || "Event Pass")} ·{" "}
<strong>{capacityStr} Claimed</strong>
</div>
<div>
<span
class="countdown-pill"
data-expires-at={event.expires_at}
>
0h 0m left · (Expires{" "}
{new Date(event.expires_at).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})})
</span>
</div>
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="btn-outline"
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.8rem;"
onclick={`openAttendeesDrawer('${event.id}', '${
encodeURIComponent(event.name)
}')`}
>
👥 Guests ({claimed})
</button>
<button
type="button"
class="btn-outline"
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.8rem;"
onclick={`rotatePin('${event.id}')`}
>
🔄 PIN
</button>
<button
type="button"
class="btn-outline extend-event-btn"
data-event-id={event.id}
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.8rem;"
>
+1h
</button>
</div>
</div>
);
})}
{/* Standard Interactive & 1:1 Delegated Sessions */}
{sessions.map((session) => {
const isCurrent = session.id === currentSessionId; const isCurrent = session.id === currentSessionId;
const isAgent = !!session.is_agent; const isAgent = !!session.is_agent;
const scopes = Array.isArray(session.custom_scopes) const scopes = Array.isArray(session.custom_scopes)
@ -116,7 +221,8 @@ export const SessionDeckFragment = ({
)} )}
</div> </div>
); );
}) })}
</>
)} )}
</div> </div>
); );

View File

@ -154,12 +154,14 @@ export const SessionsPageFragment = ({
<SessionTableFragment <SessionTableFragment
sessions={sessions} sessions={sessions}
currentSessionId={currentSessionId} currentSessionId={currentSessionId}
eventPasses={eventPasses}
/> />
{/* Mobile Adaptive Cards View */} {/* Mobile Adaptive Cards View */}
<SessionDeckFragment <SessionDeckFragment
sessions={sessions} sessions={sessions}
currentSessionId={currentSessionId} currentSessionId={currentSessionId}
eventPasses={eventPasses}
/> />
<style> <style>

View File

@ -1,10 +1,17 @@
export const SessionTableFragment = ({ export const SessionTableFragment = ({
sessions, sessions,
currentSessionId, currentSessionId,
eventPasses = [],
}: { }: {
sessions: any[]; sessions: any[];
currentSessionId: string; currentSessionId: string;
eventPasses?: any[];
}) => { }) => {
const activeEvents = (eventPasses || []).filter(
(e) => !e.expires_at || new Date(e.expires_at).getTime() > Date.now(),
);
const hasItems = sessions.length > 0 || activeEvents.length > 0;
return ( return (
<div class="card desktop-only" style="display: none;"> <div class="card desktop-only" style="display: none;">
<div class="table-container"> <div class="table-container">
@ -13,13 +20,13 @@ export const SessionTableFragment = ({
<tr> <tr>
<th>Type & Label</th> <th>Type & Label</th>
<th>Permissions</th> <th>Permissions</th>
<th>Activity</th> <th>Activity / Capacity</th>
<th>Expires</th> <th>Expires</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{sessions.length === 0 {!hasItems
? ( ? (
<tr> <tr>
<td <td
@ -31,7 +38,111 @@ export const SessionTableFragment = ({
</tr> </tr>
) )
: ( : (
sessions.map((session) => { <>
{/* Event Group Sessions */}
{activeEvents.map((event) => {
const claimed = Number(event.seats_claimed) || 0;
const maxSeats = Number(event.max_seats) || 0;
const capacityStr = maxSeats > 0
? `${claimed}/${maxSeats}`
: `${claimed}/∞`;
return (
<tr
key={`event-${event.id}`}
style="background: var(--surface-muted);"
>
<td>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.1rem;">🎟</span>
<div>
<strong style="color: var(--text-primary);">
{event.name}
</strong>
<div style="font-size: 0.75rem; color: var(--text-muted); font-family: monospace;">
PIN: {event.pin_code || "---"} · {event.slug}
</div>
</div>
</div>
</td>
<td>
<span class="badge badge-info">
{event.app_name
? `${event.app_name} (${event.role || "viewer"})`
: (event.role || "Event Pass")}
</span>
</td>
<td style="color: var(--text-secondary); font-size: 0.85rem;">
<div>
<strong style="color: var(--primary); font-size: 0.85rem;">
{capacityStr} Seats
</strong>
<div style="font-size: 0.75rem; color: var(--text-muted);">
Claimed
</div>
</div>
</td>
<td style="color: var(--text-secondary); font-size: 0.85rem;">
<span
class="countdown-pill"
data-expires-at={event.expires_at}
>
0h 0m left · (Expires{" "}
{new Date(event.expires_at).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})})
</span>
</td>
<td>
<div style="display: flex; gap: 0.35rem; align-items: center; flex-wrap: wrap;">
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 30px;"
onclick={`openAttendeesDrawer('${event.id}', '${
encodeURIComponent(event.name)
}')`}
title="Manage claimed guest seats"
>
👥 Guests ({claimed})
</button>
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 30px;"
onclick={`rotatePin('${event.id}')`}
title="Rotate PIN & Slug"
>
🔄 Rotate PIN
</button>
<button
type="button"
class="btn-outline extend-event-btn"
data-event-id={event.id}
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 30px;"
title="Extend event and guest sessions by 1 hour"
>
+1h
</button>
<button
type="button"
class="btn-danger end-event-btn"
data-event-id={event.id}
data-event-name={event.name}
style="padding: 0.25rem 0.65rem; font-size: 0.8rem; min-height: 30px;"
title="End event and revoke all guest sessions"
>
Revoke
</button>
</div>
</td>
</tr>
);
})}
{/* Standard Interactive & 1:1 Delegated Sessions */}
{sessions.map((session) => {
const isCurrent = session.id === currentSessionId; const isCurrent = session.id === currentSessionId;
const isAgent = !!session.is_agent; const isAgent = !!session.is_agent;
const scopes = Array.isArray(session.custom_scopes) const scopes = Array.isArray(session.custom_scopes)
@ -99,10 +210,13 @@ export const SessionTableFragment = ({
data-expires-at={session.expires_at} data-expires-at={session.expires_at}
> >
0h 0m left · (Expires{" "} 0h 0m left · (Expires{" "}
{new Date(session.expires_at).toLocaleTimeString([], { {new Date(session.expires_at).toLocaleTimeString(
[],
{
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
})}) },
)})
</span> </span>
</td> </td>
<td> <td>
@ -124,7 +238,9 @@ export const SessionTableFragment = ({
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 30px;" style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 30px;"
onclick={`openEditScopesModal('${session.id}', '${ onclick={`openEditScopesModal('${session.id}', '${
session.label || "Delegated" session.label || "Delegated"
}', ${JSON.stringify(JSON.stringify(scopes))})`} }', ${
JSON.stringify(JSON.stringify(scopes))
})`}
title="Edit permissions" title="Edit permissions"
> >
Scopes Scopes
@ -145,7 +261,8 @@ export const SessionTableFragment = ({
</td> </td>
</tr> </tr>
); );
}) })}
</>
)} )}
</tbody> </tbody>
</table> </table>