diff --git a/src/components/member-status-card.tsx b/src/components/member-status-card.tsx new file mode 100644 index 0000000..8427516 --- /dev/null +++ b/src/components/member-status-card.tsx @@ -0,0 +1,82 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Badge } from "@/components/ui/badge"; +import { getGitHubIssueUrl, formatTime } from "@/lib/utils"; +import type { ActiveSession } from "@/types"; +import type { getSpaceMembersWithProfiles } from "@/lib/supabase/queries"; + +type Members = Awaited>; + +interface MemberStatusCardProps { + members: Members | undefined; + activeSessions: ActiveSession[] | undefined; + dailyDurations: Record; +} + +export function MemberStatusCard({ members, activeSessions, dailyDurations }: MemberStatusCardProps) { + return ( + + + Members + + + {members && members.length > 0 ? ( +
+ {members.map(({ member, profile }) => { + const activeSession = activeSessions?.find( + (s) => s?.space_member?.user_id === member.user_id + ); + const duration = formatTime(dailyDurations[member.user_id] || 0); + return ( +
+ + + {profile.full_name?.charAt(0) || "?"} + +
+

+ {profile.full_name || "Unknown"} +

+

+ {member.role} +

+
+
+
+ + {member.status || "offline"} + + {member.status === "online" && ( + + {member.location || "remote"} + + )} +
+ {activeSession ? ( + + {activeSession.track.title} + + ) : ( + No active ticket + )} + {duration} +
+
+ ); + })} +
+ ) : ( +

No members found.

+ )} +
+
+ ); +} diff --git a/src/components/search-results-list.tsx b/src/components/search-results-list.tsx index c429cec..d497126 100644 --- a/src/components/search-results-list.tsx +++ b/src/components/search-results-list.tsx @@ -6,14 +6,26 @@ import remarkGfm from 'remark-gfm' import { Avatar, AvatarImage, AvatarFallback } from './ui/avatar' import { getTextColorForBackground } from "@/lib/utils" +import type { Track, ActiveSession, Tag } from '@/types' +import type { UseMutationResult } from '@tanstack/react-query' + interface SearchResultsListProps { searchResults: GitHubIssue[] - getTrackForIssue: (issue: GitHubIssue) => any + getTrackForIssue: (issue: GitHubIssue) => Track | null handleCreateTrack: (issue: GitHubIssue) => void handleStartSession: (trackId: string) => void - activeSession: any - startSessionMutation: any - endSessionMutation: any + activeSession: ActiveSession | null + startSessionMutation: UseMutationResult + endSessionMutation: UseMutationResult< + unknown, + unknown, + { + sessionId: string + message: string + skipSummary: boolean + selectedTags: Tag[] + } + > setShowEndSessionDialog: (show: boolean) => void isCurrentSessionTrack: (trackId: string) => boolean getSessionCount: (trackId: string) => number diff --git a/src/components/ui/multi-select.tsx b/src/components/ui/multi-select.tsx index ad5f0b5..7d4e1bf 100644 --- a/src/components/ui/multi-select.tsx +++ b/src/components/ui/multi-select.tsx @@ -133,7 +133,6 @@ export const MultiSelect = React.forwardRef< animation = 0, maxCount = 3, modalPopover = false, - asChild = false, className, ...props }, diff --git a/src/hooks/api/use-auth.tsx b/src/hooks/api/use-auth.tsx index 6087c27..a67e568 100644 --- a/src/hooks/api/use-auth.tsx +++ b/src/hooks/api/use-auth.tsx @@ -36,8 +36,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { if (!octokit) throw new Error("Octokit not initialized"); await octokit.rest.users.getAuthenticated(); return githubToken; - } catch (error: any) { - if (error?.status === 401) { + } catch (error: unknown) { + if (typeof error === 'object' && error && 'status' in error && (error as { status?: number }).status === 401) { // Token is expired, trigger a new OAuth sign-in await loginWithGitHub(); return null; diff --git a/src/hooks/api/use-space-members.ts b/src/hooks/api/use-space-members.ts index 6db036e..a36fbf3 100644 --- a/src/hooks/api/use-space-members.ts +++ b/src/hooks/api/use-space-members.ts @@ -1,7 +1,9 @@ +import React from "react"; import { useQuery } from "@tanstack/react-query"; import { getSpaceMembersWithProfiles, getActiveSession, + getClosedSessions, } from "@/lib/supabase/queries"; export function useSpaceMembers(slug: string) { @@ -19,7 +21,7 @@ export function useSpaceMembers(slug: string) { try { if (!member.user_id) return null; return await getActiveSession(member.user_id); - } catch (error) { + } catch { // It's better to return null and filter later than to throw return null; } @@ -30,9 +32,50 @@ export function useSpaceMembers(slug: string) { enabled: !!members, }); + const spaceId = members && members.length > 0 ? members[0].member.space_id : null; + + const { data: closedSessions } = useQuery({ + queryKey: ["closedSessionsToday", spaceId], + queryFn: () => getClosedSessions(spaceId || ""), + enabled: !!spaceId, + }); + + const dailyDurations = React.useMemo(() => { + if (!closedSessions) return {} as Record; + + const startOfDay = new Date(); + startOfDay.setHours(0, 0, 0, 0); + + const durations: Record = {}; + + closedSessions.forEach((session) => { + if (!session.ended_at) return; + if (new Date(session.ended_at) < startOfDay) return; + const userId = session.space_member?.user_id; + if (!userId) return; + const start = new Date(session.started_at).getTime(); + const end = new Date(session.ended_at).getTime(); + durations[userId] = (durations[userId] || 0) + (end - start); + }); + + if (activeSessions) { + const now = Date.now(); + activeSessions.forEach((session) => { + const userId = session?.space_member?.user_id; + if (!userId) return; + const start = new Date(session.started_at).getTime(); + if (start < startOfDay.getTime()) return; + durations[userId] = (durations[userId] || 0) + (now - start); + }); + } + + return durations; + }, [closedSessions, activeSessions]); + return { members, isLoading, activeSessions, + dailyDurations, }; } diff --git a/src/lib/github/queries.ts b/src/lib/github/queries.ts index b0817c7..0b84d59 100644 --- a/src/lib/github/queries.ts +++ b/src/lib/github/queries.ts @@ -26,7 +26,11 @@ export async function getRepo(org: string, repo: string) { } } -export async function searchIssues(orgs: string[] | string, query: string = '', options: {} = {}): Promise { +export async function searchIssues( + orgs: string[] | string, + query: string = '', + options: Record = {} +): Promise { const octokit = await getOctokitClient(); if (!octokit) throw new Error("Octokit client not initialized"); if (!orgs || orgs.length === 0) { diff --git a/src/lib/supabase/queries.ts b/src/lib/supabase/queries.ts index f4ebf3b..b339410 100644 --- a/src/lib/supabase/queries.ts +++ b/src/lib/supabase/queries.ts @@ -92,7 +92,7 @@ export const getUserSpaces = async () => { if (error) throw new Error(error.message); // Flatten the organizations and include role return ( - data?.map((row: any) => ({ + data?.map((row: { space: Space; role: string }) => ({ ...row.space, member_role: row.role, })) || [] diff --git a/src/routes/space/$slug/index.tsx b/src/routes/space/$slug/index.tsx index dd22c49..6b93822 100644 --- a/src/routes/space/$slug/index.tsx +++ b/src/routes/space/$slug/index.tsx @@ -13,7 +13,9 @@ import { DashboardStats } from '@/components/dashboard-stats' import { SessionActivityChart } from '@/components/session-activity-chart' import { TrackStatsChart } from '@/components/track-stats-chart' import { ActiveSessionsList } from '@/components/active-sessions-list' +import { MemberStatusCard } from '@/components/member-status-card' import { useSpaceDashboard } from '@/hooks/api/use-space-dashboard' +import { useSpaceMembers } from '@/hooks/api/use-space-members' export const Route = createFileRoute('/space/$slug/')({ component: SpaceHome, @@ -26,6 +28,7 @@ function SpaceHome() { const [openSections, setOpenSections] = useLocalStorage('space-sections', { stats: true, activeSessions: true, + members: true, sessionActivity: true, trackStats: true }) @@ -42,6 +45,12 @@ function SpaceHome() { trackStatsData, } = useSpaceDashboard(slug); + const { + members, + activeSessions: memberActiveSessions, + dailyDurations, + } = useSpaceMembers(slug); + return (
@@ -99,6 +108,25 @@ function SpaceHome() { + + {/* Members Widget */} + setOpenSections(prev => ({ ...prev, members: open }))} + className="space-y-2" + > +
+

Members

+ + + +
+ + + +
{/* Charts Section */}