feat(sessions): include active event group sessions in unified session table and mobile deck without removing event deck
This commit is contained in:
parent
2fa8222062
commit
77a82cbe84
@ -316,7 +316,7 @@ async function handleCreateEvent(e) {
|
||||
} finally {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = "🎟️ Create Event";
|
||||
btn.textContent = "Create Event";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,9 +186,9 @@ export const WorkshopPassDrawerFragment = ({
|
||||
type="submit"
|
||||
id="submitEventBtn"
|
||||
class="btn-primary"
|
||||
style="min-height: 42px; background: var(--success); border-color: var(--success);"
|
||||
style="min-height: 42px;"
|
||||
>
|
||||
🎟️ Create Event
|
||||
Create Event
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
export const SessionDeckFragment = ({
|
||||
sessions,
|
||||
currentSessionId,
|
||||
eventPasses = [],
|
||||
}: {
|
||||
sessions: any[];
|
||||
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 (
|
||||
<div
|
||||
class="mobile-only"
|
||||
style="display: flex; flex-direction: column; gap: 1rem;"
|
||||
>
|
||||
{sessions.length === 0
|
||||
{!hasItems
|
||||
? (
|
||||
<div class="card" style="text-align: center; padding: 2rem;">
|
||||
<p style="color: var(--text-muted); margin: 0;">
|
||||
@ -19,7 +26,105 @@ export const SessionDeckFragment = ({
|
||||
</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 isAgent = !!session.is_agent;
|
||||
const scopes = Array.isArray(session.custom_scopes)
|
||||
@ -116,7 +221,8 @@ export const SessionDeckFragment = ({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -154,12 +154,14 @@ export const SessionsPageFragment = ({
|
||||
<SessionTableFragment
|
||||
sessions={sessions}
|
||||
currentSessionId={currentSessionId}
|
||||
eventPasses={eventPasses}
|
||||
/>
|
||||
|
||||
{/* Mobile Adaptive Cards View */}
|
||||
<SessionDeckFragment
|
||||
sessions={sessions}
|
||||
currentSessionId={currentSessionId}
|
||||
eventPasses={eventPasses}
|
||||
/>
|
||||
|
||||
<style>
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
export const SessionTableFragment = ({
|
||||
sessions,
|
||||
currentSessionId,
|
||||
eventPasses = [],
|
||||
}: {
|
||||
sessions: any[];
|
||||
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 (
|
||||
<div class="card desktop-only" style="display: none;">
|
||||
<div class="table-container">
|
||||
@ -13,13 +20,13 @@ export const SessionTableFragment = ({
|
||||
<tr>
|
||||
<th>Type & Label</th>
|
||||
<th>Permissions</th>
|
||||
<th>Activity</th>
|
||||
<th>Activity / Capacity</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sessions.length === 0
|
||||
{!hasItems
|
||||
? (
|
||||
<tr>
|
||||
<td
|
||||
@ -31,7 +38,111 @@ export const SessionTableFragment = ({
|
||||
</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 isAgent = !!session.is_agent;
|
||||
const scopes = Array.isArray(session.custom_scopes)
|
||||
@ -99,10 +210,13 @@ export const SessionTableFragment = ({
|
||||
data-expires-at={session.expires_at}
|
||||
>
|
||||
⏳ 0h 0m left · (Expires{" "}
|
||||
{new Date(session.expires_at).toLocaleTimeString([], {
|
||||
{new Date(session.expires_at).toLocaleTimeString(
|
||||
[],
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})})
|
||||
},
|
||||
)})
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -124,7 +238,9 @@ export const SessionTableFragment = ({
|
||||
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 30px;"
|
||||
onclick={`openEditScopesModal('${session.id}', '${
|
||||
session.label || "Delegated"
|
||||
}', ${JSON.stringify(JSON.stringify(scopes))})`}
|
||||
}', ${
|
||||
JSON.stringify(JSON.stringify(scopes))
|
||||
})`}
|
||||
title="Edit permissions"
|
||||
>
|
||||
Scopes
|
||||
@ -145,7 +261,8 @@ export const SessionTableFragment = ({
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user