Compare commits

...

2 Commits

Author SHA1 Message Date
Gib
89955367aa Subscription finally working! 2025-06-13 13:36:33 -05:00
Gib
9b69027a85 Subscription finally working! 2025-06-13 13:36:13 -05:00
5 changed files with 107 additions and 8 deletions

64
src/components/status/TechTable.tsx Normal file → Executable file
View File

@ -9,7 +9,7 @@ import {
updateUserStatus, updateUserStatus,
type UserWithStatus, type UserWithStatus,
} from '@/lib/hooks/status'; } from '@/lib/hooks/status';
import { Drawer, DrawerTrigger, Progress } from '@/components/ui'; import { Drawer, DrawerTrigger, Loading } from '@/components/ui';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { HistoryDrawer } from '@/components/status'; import { HistoryDrawer } from '@/components/status';
import type { Profile } from '@/utils/supabase'; import type { Profile } from '@/utils/supabase';
@ -24,7 +24,7 @@ export const TechTable = ({
initialStatuses = [], initialStatuses = [],
className = 'w-full max-w-7xl mx-auto px-4', className = 'w-full max-w-7xl mx-auto px-4',
}: TechTableProps) => { }: TechTableProps) => {
const { isAuthenticated, profile } = useAuth(); const { isAuthenticated } = useAuth();
const { tvMode } = useTVMode(); const { tvMode } = useTVMode();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [selectedIds, setSelectedIds] = useState<string[]>([]); const [selectedIds, setSelectedIds] = useState<string[]>([]);
@ -34,6 +34,7 @@ export const TechTable = ({
useState<UserWithStatus[]>(initialStatuses); useState<UserWithStatus[]>(initialStatuses);
const [selectedHistoryUser, setSelectedHistoryUser] = const [selectedHistoryUser, setSelectedHistoryUser] =
useState<Profile | null>(null); useState<Profile | null>(null);
const channelRef = useRef<RealtimeChannel | null>(null);
const fetchRecentUsersWithStatuses = useCallback(async () => { const fetchRecentUsersWithStatuses = useCallback(async () => {
try { try {
@ -112,6 +113,63 @@ export const TechTable = ({
); );
}, [selectedIds.length, usersWithStatuses.length]); }, [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 formatTime = (timestamp: string) => {
const date = new Date(timestamp); const date = new Date(timestamp);
const time = date.toLocaleTimeString('en-US', { const time = date.toLocaleTimeString('en-US', {
@ -126,7 +184,7 @@ export const TechTable = ({
if (loading) { if (loading) {
return ( return (
<div className='flex justify-center items-center min-h-[400px]'> <div className='flex justify-center items-center min-h-[400px]'>
<Progress value={33} className='w-64' /> <Loading className='w-full' alpha={0.5} />
</div> </div>
); );
} }

View File

@ -8,6 +8,7 @@ export * from './dropdown-menu';
export * from './form'; export * from './form';
export * from './input'; export * from './input';
export * from './label'; export * from './label';
export * from './loading';
export * from './pagination'; export * from './pagination';
export * from './progress'; export * from './progress';
export * from './scroll-area'; export * from './scroll-area';

View File

@ -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<typeof ProgressPrimitive.Root> & {
/** how many ms between updates */
intervalMs?: number;
/** fraction of the remaining distance to add each tick */
alpha?: number;
};
export const Loading: React.FC<Loading_Props> = ({
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 (
<div className="items-center justify-center w-1/3 m-auto pt-20">
<Progress value={progress} className={className} {...props} />
</div>
);
};
export default Loading;

View File

@ -22,7 +22,7 @@ function Progress({
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
data-slot='progress-indicator' data-slot='progress-indicator'
className='bg-primary h-full w-full flex-1 transition-all' className='bg-primary h-full w-full flex-1 transition-all'
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
); );

View File

@ -30,14 +30,12 @@ export const getRecentUsersWithStatuses = async (): Promise<
const { data, error } = (await supabase const { data, error } = (await supabase
.from('statuses') .from('statuses')
.select( .select(`
`
user:profiles!user_id(*), user:profiles!user_id(*),
status, status,
created_at, created_at,
updated_by:profiles!updated_by_id(*) updated_by:profiles!updated_by_id(*)
`, `)
)
.gte('created_at', oneDayAgo.toISOString()) .gte('created_at', oneDayAgo.toISOString())
.order('created_at', { ascending: false })) as { .order('created_at', { ascending: false })) as {
data: UserWithStatus[]; data: UserWithStatus[];
@ -172,6 +170,7 @@ export const updateUserStatus = async (
user: userProfile, user: userProfile,
status: insertedStatus.status, status: insertedStatus.status,
created_at: insertedStatus.created_at, created_at: insertedStatus.created_at,
updated_by: userProfile,
}; };
await broadcastStatusUpdates([userStatus]); await broadcastStatusUpdates([userStatus]);