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 {
|
} finally {
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = "🎟️ Create Event";
|
btn.textContent = "Create Event";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user