Compare commits
2 Commits
b80bf9cd3f
...
89955367aa
Author | SHA1 | Date | |
---|---|---|---|
89955367aa | |||
9b69027a85 |
64
src/components/status/TechTable.tsx
Normal file → Executable file
64
src/components/status/TechTable.tsx
Normal file → Executable file
@ -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<string[]>([]);
|
||||
@ -34,6 +34,7 @@ export const TechTable = ({
|
||||
useState<UserWithStatus[]>(initialStatuses);
|
||||
const [selectedHistoryUser, setSelectedHistoryUser] =
|
||||
useState<Profile | null>(null);
|
||||
const channelRef = useRef<RealtimeChannel | null>(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 (
|
||||
<div className='flex justify-center items-center min-h-[400px]'>
|
||||
<Progress value={33} className='w-64' />
|
||||
<Loading className='w-full' alpha={0.5} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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';
|
||||
|
41
src/components/ui/loading.tsx
Normal file
41
src/components/ui/loading.tsx
Normal 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;
|
@ -22,7 +22,7 @@ function Progress({
|
||||
<ProgressPrimitive.Indicator
|
||||
data-slot='progress-indicator'
|
||||
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>
|
||||
);
|
||||
|
@ -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]);
|
||||
|
Reference in New Issue
Block a user