Tech Table & History components completely rewritten & cleaned up. Just need to implement the realtime subscription now.

This commit is contained in:
2025-06-13 11:38:33 -05:00
parent c96bdab91b
commit b80bf9cd3f
20 changed files with 639 additions and 545 deletions

View File

@ -2,12 +2,12 @@
import { ForgotPasswordCard } from '@/components/default/auth';
const ForgotPassword = () => {
return (
return (
<div className='w-full mx-auto text-center pt-2 md:pt-10'>
<div className='mx-auto flex flex-col items-center justify-center'>
<ForgotPasswordCard />
</div>
</div>
);
);
};
export default ForgotPassword;

View File

@ -1,15 +1,15 @@
'use client';
import { ProfileCard } from "@/components/default/auth";
import { ProfileCard } from '@/components/default/auth';
const ProfilePage = () => {
return (
return (
<div className='w-full mx-auto text-center pt-2 md:pt-10'>
<div className='mx-auto flex flex-col items-center justify-center'>
<ProfileCard />
</div>
</div>
);
);
};
export default ProfilePage;

View File

@ -2,13 +2,13 @@
import { SignInCard } from '@/components/default/auth';
const Login = () => {
return (
return (
<div className='w-full mx-auto text-center pt-2 md:pt-10'>
<div className='mx-auto flex flex-col items-center justify-center'>
<SignInCard />
</div>
</div>
);
);
};
export default Login;

View File

@ -1,14 +1,14 @@
'use client';
import { SignUpCard } from "@/components/default/auth";
import { SignUpCard } from '@/components/default/auth';
const SignUp = () => {
return (
return (
<div className='w-full mx-auto text-center pt-2 md:pt-10'>
<div className='mx-auto flex flex-col items-center justify-center'>
<SignUpCard />
</div>
</div>
);
);
};
export default SignUp;

View File

@ -9,8 +9,8 @@ const Home = async () => {
redirect('/sign-in');
} else if (userResponse.data) {
redirect('/status');
} else return <div/>;
}
} else return <div />;
};
export default Home;
//'use client';
@ -20,10 +20,10 @@ export default Home;
//import { useAuth } from '@/components/context';
//const HomePage = () => {
//const { isAuthenticated } = useAuth();
//if (!isAuthenticated) {
//redirect('/sign-in');
//}
//redirect('/profile');
//const { isAuthenticated } = useAuth();
//if (!isAuthenticated) {
//redirect('/sign-in');
//}
//redirect('/profile');
//};
//export default HomePage;

View File

@ -19,23 +19,23 @@ const Footer = () => {
</Link>
</div>
<div className='flex-1 flex justify-center'>
<div className='text-sm text-center space-y-1'>
<div className='flex my-auto'>
<Image
src='/favicon.png'
width={18}
height={15}
className='mr-1 mt-0.5'
alt='Tech Tracker Logo'
/>
<p>
<strong>Tech Tracker</strong> - City of Gulfport IT Department
</p>
</div>
<p className='text-muted-foreground'>
Open Source MIT Licensed Self-Hosted
</p>
</div>
<div className='text-sm text-center space-y-1'>
<div className='flex my-auto'>
<Image
src='/favicon.png'
width={18}
height={15}
className='mr-1 mt-0.5'
alt='Tech Tracker Logo'
/>
<p>
<strong>Tech Tracker</strong> - City of Gulfport IT Department
</p>
</div>
<p className='text-muted-foreground'>
Open Source MIT Licensed Self-Hosted
</p>
</div>
</div>
<div className='w-[160px]'></div> {/* Spacer to balance the layout */}
</footer>

View File

@ -62,7 +62,9 @@ const AvatarDropdown = () => {
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel className='font-bold'>{profile?.full_name}</DropdownMenuLabel>
<DropdownMenuLabel className='font-bold'>
{profile?.full_name}
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<button

View File

@ -22,62 +22,63 @@ import {
TableRow,
Button,
} from '@/components/ui';
import {
getUserHistory,
getAllHistory,
type HistoryEntry,
} from '@/lib/hooks';
import { getUserHistory, getAllHistory } from '@/lib/hooks';
import type { Profile } from '@/utils/supabase';
import type { UserWithStatus } from '@/lib/hooks';
import { toast } from 'sonner';
type HistoryDrawerProps = {
user_id: string;
user?: Profile | null;
className?: string;
};
export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
const [history, setHistory] = useState<HistoryEntry[]>([]);
export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({
user = null,
className = 'max-w-4xl mx-auto',
}) => {
const [history, setHistory] = useState<UserWithStatus[]>([]);
const [page, setPage] = useState<number>(1);
const [totalPages, setTotalPages] = useState<number>(1);
const [totalCount, setTotalCount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(true);
const perPage = 50;
const fetchHistory = useCallback(async (currentPage: number) => {
setLoading(true);
try {
let response;
if (user_id && user_id !== '') {
response = await getUserHistory(user_id, currentPage, perPage);
} else {
response = await getAllHistory(currentPage, perPage);
const fetchHistory = useCallback(
async (currentPage: number) => {
setLoading(true);
try {
let response;
if (user && user.id !== '') {
response = await getUserHistory(user.id, currentPage, perPage);
} else {
response = await getAllHistory(currentPage, perPage);
}
if (!response.success) {
throw new Error(response.error);
}
setHistory(response.data.statuses);
setTotalPages(response.data.meta.total_pages);
setTotalCount(response.data.meta.total_count);
} catch (error) {
toast.error(`Error fetching history: ${error as Error}`);
setHistory([]);
setTotalPages(1);
setTotalCount(0);
} finally {
setLoading(false);
}
if (!response.success) {
throw new Error(response.error);
}
setHistory(response.data.data);
setTotalPages(response.data.meta.total_pages);
setTotalCount(response.data.meta.total_count);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
toast.error(`Error fetching history: ${errorMessage}`);
setHistory([]);
setTotalPages(1);
setTotalCount(0);
} finally {
setLoading(false);
}
}, [user_id, perPage]);
},
[user, perPage],
);
useEffect(() => {
void fetchHistory(page);
fetchHistory(page).catch((error) => {
toast.error(`Error fetching history: ${error as Error}`);
});
}, [page, fetchHistory]);
const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages) {
setPage(newPage);
}
if (newPage >= 1 && newPage <= totalPages) setPage(newPage);
};
const formatTime = (timestamp: string) => {
@ -92,17 +93,8 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
});
};
const getDisplayName = (entry: HistoryEntry) => {
return entry.user_profile?.full_name || 'Unknown User';
};
const getUpdatedByName = (entry: HistoryEntry) => {
if (!entry.updated_by) return 'Self';
return entry.updated_by.full_name || 'Unknown';
};
return (
<DrawerContent className='max-w-4xl mx-auto'>
<DrawerContent className={className}>
<DrawerHeader>
<DrawerTitle>
<div className='flex flex-row items-center justify-center py-4'>
@ -114,7 +106,7 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
className='w-8 h-8 md:w-12 md:h-12'
/>
<h1 className='text-lg md:text-2xl lg:text-4xl font-bold pl-2 md:pl-4'>
{user_id && user_id !== '' ? 'User History' : 'All History'}
{user && user.id !== '' ? 'User History' : 'All History'}
</h1>
</div>
{totalCount > 0 && (
@ -124,7 +116,6 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
)}
</DrawerTitle>
</DrawerHeader>
<div className='px-4'>
<ScrollArea className='h-96 w-full'>
{loading ? (
@ -142,14 +133,16 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
<TableHead className='font-semibold'>Name</TableHead>
<TableHead className='font-semibold'>Status</TableHead>
<TableHead className='font-semibold'>Updated By</TableHead>
<TableHead className='font-semibold text-right'>Date & Time</TableHead>
<TableHead className='font-semibold text-right'>
Date & Time
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{history.map((entry) => (
<TableRow key={entry.id}>
<TableCell className='font-medium'>
{getDisplayName(entry)}
{entry.user.full_name}
</TableCell>
<TableCell className='max-w-xs'>
<div className='truncate' title={entry.status}>
@ -157,7 +150,7 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
</div>
</TableCell>
<TableCell className='text-sm text-muted-foreground'>
{getUpdatedByName(entry)}
{entry.updated_by?.full_name ?? ''}
</TableCell>
<TableCell className='text-right text-sm'>
{formatTime(entry.created_at)}
@ -169,7 +162,6 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
)}
</ScrollArea>
</div>
<DrawerFooter>
{totalPages > 1 && (
<Pagination>
@ -191,7 +183,9 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
<span>Page</span>
<span className='font-bold text-foreground'>{page}</span>
<span>of</span>
<span className='font-semibold text-foreground'>{totalPages}</span>
<span className='font-semibold text-foreground'>
{totalPages}
</span>
</div>
) : (
Array.from({ length: totalPages }).map((_, idx) => (

View File

@ -9,13 +9,10 @@ import {
updateUserStatus,
type UserWithStatus,
} from '@/lib/hooks/status';
import {
Drawer,
DrawerTrigger,
Progress,
} from '@/components/ui';
import { Drawer, DrawerTrigger, Progress } from '@/components/ui';
import { toast } from 'sonner';
import { HistoryDrawer } from '@/components/status';
import type { Profile } from '@/utils/supabase';
import type { RealtimeChannel } from '@supabase/supabase-js';
type TechTableProps = {
@ -33,10 +30,10 @@ export const TechTable = ({
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [selectAll, setSelectAll] = useState(false);
const [statusInput, setStatusInput] = useState('');
const [usersWithStatuses, setUsersWithStatuses] = useState<UserWithStatus[]>(initialStatuses);
const [selectedHistoryUserId, setSelectedHistoryUserId] = useState('');
const supabase = createClient();
const [usersWithStatuses, setUsersWithStatuses] =
useState<UserWithStatus[]>(initialStatuses);
const [selectedHistoryUser, setSelectedHistoryUser] =
useState<Profile | null>(null);
const fetchRecentUsersWithStatuses = useCallback(async () => {
try {
@ -44,7 +41,7 @@ export const TechTable = ({
if (!response.success) throw new Error(response.error);
return response.data;
} catch (error) {
toast.error(`Error fetching technicians: ${error as Error}`)
toast.error(`Error fetching technicians: ${error as Error}`);
return [];
}
}, []);
@ -78,19 +75,24 @@ export const TechTable = ({
} else {
const result = await updateStatuses(selectedIds, statusInput);
if (!result.success) throw new Error(result.error);
toast.success(`Status updated for ${selectedIds.length} selected users.`);
toast.success(
`Status updated for ${selectedIds.length} selected users.`,
);
}
setSelectedIds([]);
setStatusInput('');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to update status: ${errorMessage}`);
}
}, [isAuthenticated, statusInput, selectedIds, usersWithStatuses, profile]);
}, [isAuthenticated, statusInput, selectedIds]);
const handleCheckboxChange = (id: string) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev.filter(prevId => prevId !== id) : [...prev, id]
prev.includes(id)
? prev.filter((prevId) => prevId !== id)
: [...prev, id],
);
};
@ -98,7 +100,7 @@ export const TechTable = ({
if (selectAll) {
setSelectedIds([]);
} else {
setSelectedIds(usersWithStatuses.map(tech => tech.user.id));
setSelectedIds(usersWithStatuses.map((tech) => tech.user.id));
}
setSelectAll(!selectAll);
};
@ -106,7 +108,7 @@ export const TechTable = ({
useEffect(() => {
setSelectAll(
selectedIds.length === usersWithStatuses.length &&
usersWithStatuses.length > 0
usersWithStatuses.length > 0,
);
}, [selectedIds.length, usersWithStatuses.length]);
@ -116,9 +118,9 @@ export const TechTable = ({
hour: 'numeric',
minute: 'numeric',
});
const day = date.getDate()
const day = date.getDate();
const month = date.toLocaleString('default', { month: 'long' });
return `${time} = ${month} ${day}`;
return `${time} - ${month} ${day}`;
};
if (loading) {
@ -131,7 +133,10 @@ export const TechTable = ({
return (
<div className={className}>
<table className={`w-full text-center border-collapse ${tvMode ? 'text-4xl lg:text-5xl' : 'text-base lg:text-lg'}`}>
<table
className={`w-full text-center border-collapse \
${tvMode ? 'text-4xl lg:text-5xl' : 'text-base lg:text-lg'}`}
>
<thead>
<tr className='bg-muted'>
{!tvMode && (
@ -150,7 +155,7 @@ export const TechTable = ({
<DrawerTrigger className='hover:underline'>
Status
</DrawerTrigger>
<HistoryDrawer user_id='' />
<HistoryDrawer />
</Drawer>
</th>
<th className='py-3 px-4 border font-semibold'>Updated At</th>
@ -171,7 +176,9 @@ export const TechTable = ({
type='checkbox'
className='scale-125 cursor-pointer'
checked={selectedIds.includes(userWithStatus.user.id)}
onChange={() => handleCheckboxChange(userWithStatus.user.id)}
onChange={() =>
handleCheckboxChange(userWithStatus.user.id)
}
/>
</td>
)}
@ -182,15 +189,12 @@ export const TechTable = ({
<Drawer>
<DrawerTrigger
className='text-left w-full p-2 rounded hover:bg-muted transition-colors'
onClick={() => setSelectedHistoryUserId(userWithStatus.user.id)}
onClick={() => setSelectedHistoryUser(userWithStatus.user)}
>
{userWithStatus.status}
</DrawerTrigger>
{selectedHistoryUserId === userWithStatus.user.id && (
<HistoryDrawer
key={selectedHistoryUserId}
user_id={selectedHistoryUserId}
/>
{selectedHistoryUser === userWithStatus.user && (
<HistoryDrawer user={selectedHistoryUser} />
)}
</Drawer>
</td>
@ -201,7 +205,43 @@ export const TechTable = ({
))}
</tbody>
</table>
{!tvMode && (
<div className='mx-auto flex flex-row items-center justify-center py-5 gap-4'>
<input
autoFocus
type='text'
placeholder='New Status'
className={
'min-w-[120px] lg:min-w-[400px] py-2 px-3 rounded-xl \
border bg-background lg:text-2xl focus:outline-none \
focus:ring-2 focus:ring-primary'
}
value={statusInput}
onChange={(e) => setStatusInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
updateStatus().catch((error) => {
toast.error(`Failed to update status: ${error as Error}`);
});
}
}}
/>
<button
type='submit'
className={
'min-w-[100px] lg:min-w-[160px] py-2 px-4 rounded-xl \
text-center font-semibold lg:text-2xl bg-primary \
text-primary-foreground hover:bg-primary/90 \
transition-colors disabled:opacity-50 \
disabled:cursor-not-allowed'
}
onClick={() => void updateStatus()}
disabled={!statusInput.trim()}
>
Update
</button>
</div>
)}
</div>
);
};

View File

@ -1,32 +1,32 @@
"use client"
'use client';
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Drawer({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
return <DrawerPrimitive.Root data-slot='drawer' {...props} />;
}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
return <DrawerPrimitive.Trigger data-slot='drawer-trigger' {...props} />;
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
return <DrawerPrimitive.Portal data-slot='drawer-portal' {...props} />;
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
return <DrawerPrimitive.Close data-slot='drawer-close' {...props} />;
}
function DrawerOverlay({
@ -35,14 +35,14 @@ function DrawerOverlay({
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return (
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
data-slot='drawer-overlay'
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
/>
)
);
}
function DrawerContent({
@ -51,48 +51,48 @@ function DrawerContent({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
return (
<DrawerPortal data-slot="drawer-portal">
<DrawerPortal data-slot='drawer-portal'>
<DrawerOverlay />
<DrawerPrimitive.Content
data-slot="drawer-content"
data-slot='drawer-content'
className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
className
'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',
'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b',
'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t',
'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm',
'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm',
className,
)}
{...props}
>
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
<div className='bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block' />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
)
);
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="drawer-header"
data-slot='drawer-header'
className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
className
'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left',
className,
)}
{...props}
/>
)
);
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
data-slot='drawer-footer'
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
)
);
}
function DrawerTitle({
@ -101,11 +101,11 @@ function DrawerTitle({
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("text-foreground font-semibold", className)}
data-slot='drawer-title'
className={cn('text-foreground font-semibold', className)}
{...props}
/>
)
);
}
function DrawerDescription({
@ -114,11 +114,11 @@ function DrawerDescription({
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("text-muted-foreground text-sm", className)}
data-slot='drawer-description'
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
);
}
export {
@ -132,4 +132,4 @@ export {
DrawerFooter,
DrawerTitle,
DrawerDescription,
}
};

View File

@ -1,68 +1,68 @@
import * as React from "react"
import * as React from 'react';
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react"
} from 'lucide-react';
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
import { cn } from '@/lib/utils';
import { Button, buttonVariants } from '@/components/ui/button';
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
role='navigation'
aria-label='pagination'
data-slot='pagination'
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
)
);
}
function PaginationContent({
className,
...props
}: React.ComponentProps<"ul">) {
}: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)}
data-slot='pagination-content'
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
)
);
}
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
return <li data-slot="pagination-item" {...props} />
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot='pagination-item' {...props} />;
}
type PaginationLinkProps = {
isActive?: boolean
} & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a">
isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>;
function PaginationLink({
className,
isActive,
size = "icon",
size = 'icon',
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
aria-current={isActive ? 'page' : undefined}
data-slot='pagination-link'
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
variant: isActive ? 'outline' : 'ghost',
size,
}),
className
className,
)}
{...props}
/>
)
);
}
function PaginationPrevious({
@ -71,15 +71,15 @@ function PaginationPrevious({
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
aria-label='Go to previous page'
size='default'
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
<span className='hidden sm:block'>Previous</span>
</PaginationLink>
)
);
}
function PaginationNext({
@ -88,32 +88,32 @@ function PaginationNext({
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
aria-label='Go to next page'
size='default'
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props}
>
<span className="hidden sm:block">Next</span>
<span className='hidden sm:block'>Next</span>
<ChevronRightIcon />
</PaginationLink>
)
);
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
}: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn("flex size-9 items-center justify-center", className)}
data-slot='pagination-ellipsis'
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
<MoreHorizontalIcon className='size-4' />
<span className='sr-only'>More pages</span>
</span>
)
);
}
export {
@ -124,4 +124,4 @@ export {
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}
};

View File

@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Progress({
className,
@ -12,20 +12,20 @@ function Progress({
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root
data-slot="progress"
data-slot='progress'
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
className
'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
className,
)}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
data-slot='progress-indicator'
className='bg-primary h-full w-full flex-1 transition-all'
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
)
);
}
export { Progress }
export { Progress };

View File

@ -1,9 +1,9 @@
"use client"
'use client';
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function ScrollArea({
className,
@ -12,47 +12,47 @@ function ScrollArea({
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
data-slot='scroll-area'
className={cn('relative', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
data-slot='scroll-area-viewport'
className='focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1'
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
);
}
function ScrollBar({
className,
orientation = "vertical",
orientation = 'vertical',
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
data-slot='scroll-area-scrollbar'
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
'flex touch-none p-px transition-colors select-none',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent',
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
data-slot='scroll-area-thumb'
className='bg-border relative flex-1 rounded-full'
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
);
}
export { ScrollArea, ScrollBar }
export { ScrollArea, ScrollBar };

View File

@ -1,107 +1,107 @@
"use client"
'use client';
import * as React from "react"
import * as React from 'react';
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils';
function Table({ className, ...props }: React.ComponentProps<"table">) {
function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<div
data-slot="table-container"
className="relative w-full overflow-x-auto"
data-slot='table-container'
className='relative w-full overflow-x-auto'
>
<table
data-slot="table"
className={cn("w-full caption-bottom text-sm", className)}
data-slot='table'
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
)
);
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
data-slot='table-header'
className={cn('[&_tr]:border-b', className)}
{...props}
/>
)
);
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return (
<tbody
data-slot="table-body"
className={cn("[&_tr:last-child]:border-0", className)}
data-slot='table-body'
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
)
);
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return (
<tfoot
data-slot="table-footer"
data-slot='table-footer'
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
className
'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
/>
)
);
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return (
<tr
data-slot="table-row"
data-slot='table-row'
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className,
)}
{...props}
/>
)
);
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
<th
data-slot="table-head"
data-slot='table-head'
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
/>
)
);
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
<td
data-slot="table-cell"
data-slot='table-cell'
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
/>
)
);
}
function TableCaption({
className,
...props
}: React.ComponentProps<"caption">) {
}: React.ComponentProps<'caption'>) {
return (
<caption
data-slot="table-caption"
className={cn("text-muted-foreground mt-4 text-sm", className)}
data-slot='table-caption'
className={cn('text-muted-foreground mt-4 text-sm', className)}
{...props}
/>
)
);
}
export {
@ -113,4 +113,4 @@ export {
TableRow,
TableCell,
TableCaption,
}
};

View File

@ -1,9 +1,10 @@
'use server';
import { createServerClient } from '@/utils/supabase';
import type { Profile, Result, Status } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks'
import type { Profile, Result } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks';
type UserWithStatus = {
export type UserWithStatus = {
id?: string;
user: Profile;
status: string;
created_at: string;
@ -11,8 +12,7 @@ type UserWithStatus = {
};
type PaginatedHistory = {
profile?: Profile;
statuses: Status[];
statuses: UserWithStatus[];
meta: {
current_page: number;
per_page: number;
@ -28,16 +28,21 @@ export const getRecentUsersWithStatuses = async (): Promise<
const supabase = await createServerClient();
const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24);
const { data, error } = await supabase
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[], error: unknown };
.order('created_at', { ascending: false })) as {
data: UserWithStatus[];
error: unknown;
};
if (error) throw error as Error;
if (!data?.length) return { success: true, data: [] };
@ -69,10 +74,12 @@ export const broadcastStatusUpdates = async (
payload: {
user_status: userStatus,
timestamp: new Date().toISOString(),
}
},
});
if (broadcast === 'error' || broadcast === 'timed out')
throw new Error('Failed to broadcast status update. Timed out or errored.');
throw new Error(
'Failed to broadcast status update. Timed out or errored.',
);
}
return { success: true, data: undefined };
} catch (error) {
@ -96,16 +103,14 @@ export const updateStatuses = async (
const user = userResponse.data;
const userProfile = profileResponse.data;
const inserts = userIds.map(usersId => ({
const inserts = userIds.map((usersId) => ({
user_id: usersId,
status,
updated_by_id: user.id,
}));
const { data: insertedStatuses, error: insertedStatusesError } = await supabase
.from('statuses')
.insert(inserts)
.select();
const { data: insertedStatuses, error: insertedStatusesError } =
await supabase.from('statuses').insert(inserts).select();
if (insertedStatusesError) throw insertedStatusesError as Error;
if (insertedStatuses) {
@ -130,7 +135,6 @@ export const updateStatuses = async (
await broadcastStatusUpdates(broadcastArray);
}
return { success: true, data: undefined };
} catch (error) {
return {
success: false,
@ -139,13 +143,17 @@ export const updateStatuses = async (
}
};
export const updateUserStatus = async (status: string): Promise<Result<void>> => {
export const updateUserStatus = async (
status: string,
): Promise<Result<void>> => {
try {
const supabase = await createServerClient();
const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`);
if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const profileResponse = await getProfile();
if (!profileResponse.success) throw new Error(`Could not get profile! ${profileResponse.error}`);
if (!profileResponse.success)
throw new Error(`Could not get profile! ${profileResponse.error}`);
const user = userResponse.data;
const userProfile = profileResponse.data;
@ -168,12 +176,11 @@ export const updateUserStatus = async (status: string): Promise<Result<void>> =>
await broadcastStatusUpdates([userStatus]);
return { success: true, data: undefined };
} catch (error) {
return {
success: false,
error: `Error updating user's status: ${error as Error}`,
}
};
}
};
@ -185,7 +192,8 @@ export const getUserHistory = async (
try {
const supabase = await createServerClient();
const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`);
if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage;
const { count } = await supabase
@ -193,19 +201,30 @@ export const getUserHistory = async (
.select('*', { count: 'exact', head: true })
.eq('user_id', userId);
const { data: statuses, error: statusesError } = await supabase
const { data: statuses, error: statusesError } = (await supabase
.from('statuses')
.select('*')
.select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.eq('user_id', userId)
.order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown};
.range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error;
const { data: profile, error: profileError } = await supabase
const { data: profile, error: profileError } = (await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single() as { data: Profile, error: unknown };
.single()) as { data: Profile; error: unknown };
if (profileError) throw profileError as Error;
if (!profile) throw new Error('User profile not found!');
@ -215,7 +234,6 @@ export const getUserHistory = async (
return {
success: true,
data: {
profile,
statuses,
meta: {
current_page: page,
@ -225,7 +243,6 @@ export const getUserHistory = async (
},
},
};
} catch (error) {
return {
success: false,
@ -241,18 +258,30 @@ export const getAllHistory = async (
try {
const supabase = await createServerClient();
const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`);
if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage;
const { count } = await supabase
.from('statuses')
.select('*', { count: 'exact', head: true });
const { data: statuses, error: statusesError } = await supabase
const { data: statuses, error: statusesError } = (await supabase
.from('statuses')
.select('*')
.select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown};
.range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error;
const totalCount = count ?? 0;
@ -270,7 +299,6 @@ export const getAllHistory = async (
},
},
};
} catch (error) {
return {
success: false,

View File

@ -146,6 +146,9 @@ export const getUser = async (): Promise<Result<User>> => {
if (error) throw error;
return { success: true, data: data.user };
} catch (error) {
return { success: false, error: `Could not get user! Error: ${error as Error}` };
return {
success: false,
error: `Could not get user! Error: ${error as Error}`,
};
}
};

View File

@ -1,9 +1,10 @@
'use client';
import { createClient } from '@/utils/supabase';
import type { Profile, Result, Status } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks'
import type { Profile, Result } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks';
export type UserWithStatus = {
id?: string;
user: Profile;
status: string;
created_at: string;
@ -11,8 +12,7 @@ export type UserWithStatus = {
};
type PaginatedHistory = {
profile?: Profile;
statuses: Status[];
statuses: UserWithStatus[];
meta: {
current_page: number;
per_page: number;
@ -28,16 +28,21 @@ export const getRecentUsersWithStatuses = async (): Promise<
const supabase = createClient();
const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24);
const { data, error } = await supabase
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[], error: unknown };
.order('created_at', { ascending: false })) as {
data: UserWithStatus[];
error: unknown;
};
if (error) throw error as Error;
if (!data?.length) return { success: true, data: [] };
@ -69,10 +74,12 @@ export const broadcastStatusUpdates = async (
payload: {
user_status: userStatus,
timestamp: new Date().toISOString(),
}
},
});
if (broadcast === 'error' || broadcast === 'timed out')
throw new Error('Failed to broadcast status update. Timed out or errored.');
throw new Error(
'Failed to broadcast status update. Timed out or errored.',
);
}
return { success: true, data: undefined };
} catch (error) {
@ -96,16 +103,14 @@ export const updateStatuses = async (
const user = userResponse.data;
const userProfile = profileResponse.data;
const inserts = userIds.map(usersId => ({
const inserts = userIds.map((usersId) => ({
user_id: usersId,
status,
updated_by_id: user.id,
}));
const { data: insertedStatuses, error: insertedStatusesError } = await supabase
.from('statuses')
.insert(inserts)
.select();
const { data: insertedStatuses, error: insertedStatusesError } =
await supabase.from('statuses').insert(inserts).select();
if (insertedStatusesError) throw insertedStatusesError as Error;
if (insertedStatuses) {
@ -130,7 +135,6 @@ export const updateStatuses = async (
await broadcastStatusUpdates(broadcastArray);
}
return { success: true, data: undefined };
} catch (error) {
return {
success: false,
@ -139,13 +143,17 @@ export const updateStatuses = async (
}
};
export const updateUserStatus = async (status: string): Promise<Result<void>> => {
export const updateUserStatus = async (
status: string,
): Promise<Result<void>> => {
try {
const supabase = createClient();
const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`);
if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const profileResponse = await getProfile();
if (!profileResponse.success) throw new Error(`Could not get profile! ${profileResponse.error}`);
if (!profileResponse.success)
throw new Error(`Could not get profile! ${profileResponse.error}`);
const user = userResponse.data;
const userProfile = profileResponse.data;
@ -168,12 +176,11 @@ export const updateUserStatus = async (status: string): Promise<Result<void>> =>
await broadcastStatusUpdates([userStatus]);
return { success: true, data: undefined };
} catch (error) {
return {
success: false,
error: `Error updating user's status: ${error as Error}`,
}
};
}
};
@ -185,7 +192,8 @@ export const getUserHistory = async (
try {
const supabase = createClient();
const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`);
if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage;
const { count } = await supabase
@ -193,19 +201,30 @@ export const getUserHistory = async (
.select('*', { count: 'exact', head: true })
.eq('user_id', userId);
const { data: statuses, error: statusesError } = await supabase
const { data: statuses, error: statusesError } = (await supabase
.from('statuses')
.select('*')
.select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.eq('user_id', userId)
.order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown};
.range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error;
const { data: profile, error: profileError } = await supabase
const { data: profile, error: profileError } = (await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single() as { data: Profile, error: unknown };
.single()) as { data: Profile; error: unknown };
if (profileError) throw profileError as Error;
if (!profile) throw new Error('User profile not found!');
@ -215,7 +234,6 @@ export const getUserHistory = async (
return {
success: true,
data: {
profile,
statuses,
meta: {
current_page: page,
@ -225,7 +243,6 @@ export const getUserHistory = async (
},
},
};
} catch (error) {
return {
success: false,
@ -241,18 +258,30 @@ export const getAllHistory = async (
try {
const supabase = createClient();
const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`);
if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage;
const { count } = await supabase
.from('statuses')
.select('*', { count: 'exact', head: true });
const { data: statuses, error: statusesError } = await supabase
const { data: statuses, error: statusesError } = (await supabase
.from('statuses')
.select('*')
.select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown};
.range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error;
const totalCount = count ?? 0;
@ -270,12 +299,10 @@ export const getAllHistory = async (
},
},
};
} catch (error) {
return {
success: false,
error: `Error getting all history: ${error as Error}`,
}
};
}
};

View File

@ -4,200 +4,200 @@ export type Json =
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
| Json[];
export type Database = {
public: {
Tables: {
profiles: {
Row: {
avatar_url: string | null
email: string | null
full_name: string | null
id: string
provider: string | null
updated_at: string | null
}
avatar_url: string | null;
email: string | null;
full_name: string | null;
id: string;
provider: string | null;
updated_at: string | null;
};
Insert: {
avatar_url?: string | null
email?: string | null
full_name?: string | null
id: string
provider?: string | null
updated_at?: string | null
}
avatar_url?: string | null;
email?: string | null;
full_name?: string | null;
id: string;
provider?: string | null;
updated_at?: string | null;
};
Update: {
avatar_url?: string | null
email?: string | null
full_name?: string | null
id?: string
provider?: string | null
updated_at?: string | null
}
Relationships: []
}
avatar_url?: string | null;
email?: string | null;
full_name?: string | null;
id?: string;
provider?: string | null;
updated_at?: string | null;
};
Relationships: [];
};
statuses: {
Row: {
created_at: string
id: string
status: string
updated_by_id: string | null
user_id: string
}
created_at: string;
id: string;
status: string;
updated_by_id: string | null;
user_id: string;
};
Insert: {
created_at?: string
id?: string
status: string
updated_by_id?: string | null
user_id: string
}
created_at?: string;
id?: string;
status: string;
updated_by_id?: string | null;
user_id: string;
};
Update: {
created_at?: string
id?: string
status?: string
updated_by_id?: string | null
user_id?: string
}
created_at?: string;
id?: string;
status?: string;
updated_by_id?: string | null;
user_id?: string;
};
Relationships: [
{
foreignKeyName: "statuses_updated_by_id_fkey"
columns: ["updated_by_id"]
isOneToOne: false
referencedRelation: "profiles"
referencedColumns: ["id"]
foreignKeyName: 'statuses_updated_by_id_fkey';
columns: ['updated_by_id'];
isOneToOne: false;
referencedRelation: 'profiles';
referencedColumns: ['id'];
},
{
foreignKeyName: "statuses_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "profiles"
referencedColumns: ["id"]
foreignKeyName: 'statuses_user_id_fkey';
columns: ['user_id'];
isOneToOne: false;
referencedRelation: 'profiles';
referencedColumns: ['id'];
},
]
}
}
];
};
};
Views: {
[_ in never]: never
}
[_ in never]: never;
};
Functions: {
[_ in never]: never
}
[_ in never]: never;
};
Enums: {
[_ in never]: never
}
[_ in never]: never;
};
CompositeTypes: {
[_ in never]: never
}
}
}
[_ in never]: never;
};
};
};
type DefaultSchema = Database[Extract<keyof Database, "public">]
type DefaultSchema = Database[Extract<keyof Database, 'public'>];
export type Tables<
DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
| keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database
schema: keyof Database;
}
? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
? keyof (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R
? (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends {
Row: infer R;
}
? R
: never
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] &
DefaultSchema["Views"])
? (DefaultSchema["Tables"] &
DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] &
DefaultSchema['Views'])
? (DefaultSchema['Tables'] &
DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R;
}
? R
: never
: never
: never;
export type TablesInsert<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| keyof DefaultSchema['Tables']
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database
schema: keyof Database;
}
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables']
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I
? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
Insert: infer I;
}
? I
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I;
}
? I
: never
: never
: never;
export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| keyof DefaultSchema['Tables']
| { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database
schema: keyof Database;
}
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables']
: never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U
? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
Update: infer U;
}
? U
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Update: infer U
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
Update: infer U;
}
? U
: never
: never
: never;
export type Enums<
DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema["Enums"]
| keyof DefaultSchema['Enums']
| { schema: keyof Database },
EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof Database
schema: keyof Database;
}
? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
? keyof Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums']
: never = never,
> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
: never
? Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums']
? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions]
: never;
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema["CompositeTypes"]
| keyof DefaultSchema['CompositeTypes']
| { schema: keyof Database },
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof Database
schema: keyof Database;
}
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes']
: never = never,
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
: never
? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes']
? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions]
: never;
export const Constants = {
public: {
Enums: {},
},
} as const
} as const;