diff --git a/src/components/status/TechTable.tsx b/src/components/status/TechTable.tsx old mode 100644 new mode 100755 index 0268011..2340c3c --- a/src/components/status/TechTable.tsx +++ b/src/components/status/TechTable.tsx @@ -9,7 +9,7 @@ import { updateUserStatus, type UserWithStatus, } from '@/lib/hooks/status'; -import { Drawer, DrawerTrigger, Progress } from '@/components/ui'; +import { Drawer, DrawerTrigger, Loading } from '@/components/ui'; import { toast } from 'sonner'; import { HistoryDrawer } from '@/components/status'; import type { Profile } from '@/utils/supabase'; @@ -24,7 +24,7 @@ export const TechTable = ({ initialStatuses = [], className = 'w-full max-w-7xl mx-auto px-4', }: TechTableProps) => { - const { isAuthenticated, profile } = useAuth(); + const { isAuthenticated } = useAuth(); const { tvMode } = useTVMode(); const [loading, setLoading] = useState(true); const [selectedIds, setSelectedIds] = useState([]); @@ -34,6 +34,7 @@ export const TechTable = ({ useState(initialStatuses); const [selectedHistoryUser, setSelectedHistoryUser] = useState(null); + const channelRef = useRef(null); const fetchRecentUsersWithStatuses = useCallback(async () => { try { @@ -112,6 +113,63 @@ export const TechTable = ({ ); }, [selectedIds.length, usersWithStatuses.length]); + useEffect(() => { + if (!isAuthenticated) return; + const supabase = createClient(); + + const channel = supabase + .channel('status_updates', { + config: { broadcast: { self: true }} + }) + .on('broadcast', { event: 'status_updated' }, (payload) => { + const { user_status } = payload.payload as { + user_status: UserWithStatus; + timestamp: string; + }; + console.log('Received status update:', user_status); + + setUsersWithStatuses((prevUsers) => { + const existingUserIndex = prevUsers.findIndex((u) => + u.user.id === user_status.user.id, + ); + + if (existingUserIndex !== -1) { + const updatedUsers = [...prevUsers]; + updatedUsers[existingUserIndex] = { + user: user_status.user, // Use the user from the broadcast + status: user_status.status, + created_at: user_status.created_at, + updated_by: user_status.updated_by, + }; + return updatedUsers; + } else { + // Add new user to list! + return [user_status, ...prevUsers]; + } + }); + }) + .subscribe((status) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + if (status === 'SUBSCRIBED') { + console.log('Successfully subscribed to status updates!'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + } else if (status === 'CHANNEL_ERROR') { + console.error('Error subscribing to status updates.') + } + }); + + channelRef.current = channel; + + return () => { + if (channelRef.current) { + supabase.removeChannel(channelRef.current).catch((error) => { + console.error(`Error unsubscribing from status updates: ${error}`); + }); + channelRef.current = null; + } + }; + }, [isAuthenticated]); + const formatTime = (timestamp: string) => { const date = new Date(timestamp); const time = date.toLocaleTimeString('en-US', { @@ -126,7 +184,7 @@ export const TechTable = ({ if (loading) { return (
- +
); } diff --git a/src/components/status/_TechTable.tsx b/src/components/status/_TechTable.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/ui/index.tsx b/src/components/ui/index.tsx index 1661938..26231b1 100644 --- a/src/components/ui/index.tsx +++ b/src/components/ui/index.tsx @@ -8,6 +8,7 @@ export * from './dropdown-menu'; export * from './form'; export * from './input'; export * from './label'; +export * from './loading'; export * from './pagination'; export * from './progress'; export * from './scroll-area'; diff --git a/src/components/ui/loading.tsx b/src/components/ui/loading.tsx new file mode 100644 index 0000000..cc43435 --- /dev/null +++ b/src/components/ui/loading.tsx @@ -0,0 +1,41 @@ +'use client'; +import * as React from 'react'; +import { Progress } from '@/components/ui/progress'; +import type * as ProgressPrimitive from '@radix-ui/react-progress'; + +type Loading_Props = React.ComponentProps & { + /** how many ms between updates */ + intervalMs?: number; + /** fraction of the remaining distance to add each tick */ + alpha?: number; +}; + +export const Loading: React.FC = ({ + intervalMs = 50, + alpha = 0.1, + className, + ...props +}: Loading_Props) => { + const [progress, setProgress] = React.useState(0); + + React.useEffect(() => { + const id = window.setInterval(() => { + setProgress((prev) => { + // compute the next progress + const next = prev + (100 - prev) * alpha; + // optional: round if you want neat numbers + return Math.min(100, Math.round(next * 10) / 10); + }); + }, intervalMs); + + return () => window.clearInterval(id); + }, [intervalMs, alpha]); + + return ( +
+ +
+ ); +}; + +export default Loading; diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx index f89bd85..f918f3a 100644 --- a/src/components/ui/progress.tsx +++ b/src/components/ui/progress.tsx @@ -22,7 +22,7 @@ function Progress({ ); diff --git a/src/lib/hooks/status.ts b/src/lib/hooks/status.ts index 44f2871..e058c76 100644 --- a/src/lib/hooks/status.ts +++ b/src/lib/hooks/status.ts @@ -30,14 +30,12 @@ export const getRecentUsersWithStatuses = async (): Promise< const { data, error } = (await supabase .from('statuses') - .select( - ` + .select(` user:profiles!user_id(*), status, created_at, updated_by:profiles!updated_by_id(*) - `, - ) + `) .gte('created_at', oneDayAgo.toISOString()) .order('created_at', { ascending: false })) as { data: UserWithStatus[]; @@ -172,6 +170,7 @@ export const updateUserStatus = async ( user: userProfile, status: insertedStatus.status, created_at: insertedStatus.created_at, + updated_by: userProfile, }; await broadcastStatusUpdates([userStatus]);