Cleaning up. Rewriting. Vibing. Coding. Little bit of vibe coding.

This commit is contained in:
2025-06-19 14:31:10 -05:00
parent 3dabc27d58
commit d0533b78e8
15 changed files with 697 additions and 412 deletions

View File

@ -48,7 +48,8 @@ const Header = () => {
height={100}
className='max-w-[40px] md:max-w-[120px]'
/>
<h1 className='title-text text-sm md:text-4xl lg:text-8xl
<h1
className='title-text text-sm md:text-4xl lg:text-8xl
bg-gradient-to-r from-[#281A65] via-[#363354] to-accent-foreground
dark:from-[#bec8e6] dark:via-[#F0EEE4] dark:to-[#FFF8E7]
font-bold pl-2 md:pl-12 text-transparent bg-clip-text'

View File

@ -8,7 +8,7 @@ type ConnectionStatusProps = {
onReconnect?: () => void;
showAsButton?: boolean;
className?: string;
}
};
const getConnectionIcon = (status: ConnectionStatusType) => {
switch (status) {
@ -57,10 +57,7 @@ export const ConnectionStatus = ({
}
return (
<Badge
variant='outline'
className={`flex items-center gap-2 ${className}`}
>
<Badge variant='outline' className={`flex items-center gap-2 ${className}`}>
{getConnectionIcon(status)}
<span className='text-base'>{getConnectionText(status)}</span>
</Badge>

View File

@ -106,7 +106,9 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({
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 && user.id !== '' ? `${user.full_name}'s History` : 'All History'}
{user && user.id !== ''
? `${user.full_name}'s History`
: 'All History'}
</h1>
</div>
{totalCount > 0 && (

View File

@ -12,12 +12,12 @@ import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { RefreshCw, Clock, Calendar } from 'lucide-react';
import { useStatusData, useSharedStatusSubscription } from '@/lib/hooks';
import { useStatusData, useStatusSubscription } from '@/lib/hooks';
import { formatTime, formatDate } from '@/lib/utils';
import Link from 'next/link';
type ListProps = {
initialStatuses: UserWithStatus[]
initialStatuses: UserWithStatus[];
};
export const StatusList = ({ initialStatuses = [] }: ListProps) => {
@ -44,7 +44,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
});
// In your StatusList component
const { connectionStatus, connect: reconnect } = useSharedStatusSubscription(() => {
const { connectionStatus, connect: reconnect } = useStatusSubscription(() => {
refetch().catch((error) => {
console.error('Error refetching statuses:', error);
});
@ -53,14 +53,14 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
const handleUpdateStatus = () => {
if (!isAuthenticated) {
setUpdateStatusMessage(
'Error: You must be signed in to update technician statuses!'
'Error: You must be signed in to update technician statuses!',
);
return;
}
if (statusInput.length < 3 || statusInput.length > 80) {
setUpdateStatusMessage(
'Error: Your status must be between 3 & 80 characters long!'
'Error: Your status must be between 3 & 80 characters long!',
);
return;
}
@ -79,7 +79,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
setSelectedUsers((prev) =>
prev.some((u) => u.user.id === user.user.id)
? prev.filter((prevUser) => prevUser.user.id !== user.user.id)
: [...prev, user]
: [...prev, user],
);
};
@ -95,7 +95,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
useEffect(() => {
setSelectAll(
selectedUsers.length === usersWithStatuses.length &&
usersWithStatuses.length > 0
usersWithStatuses.length > 0,
);
}, [selectedUsers.length, usersWithStatuses.length]);
@ -174,7 +174,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
</Link>
</div>
)}
</div>
</div>
<div className='flex items-center gap-2'>
<ConnectionStatus
status={connectionStatus}
@ -187,7 +187,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
<div className={cardContainerClassName}>
{usersWithStatuses.map((userWithStatus) => {
const isSelected = selectedUsers.some(
(u) => u.user.id === userWithStatus.user.id
(u) => u.user.id === userWithStatus.user.id,
);
const isNewStatus = newStatuses.has(userWithStatus);
const isUpdatedByOther =
@ -329,8 +329,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
{selectedUsers.length > 0
? `Update status for ${selectedUsers.length}
${selectedUsers.length > 1 ? 'users' : 'user'}`
: 'Update status'
}
: 'Update status'}
</SubmitButton>
</div>
{updateStatusMessage &&
@ -341,7 +340,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
<StatusMessage message={{ error: updateStatusMessage }} />
) : (
<StatusMessage message={{ message: updateStatusMessage }} />
))}
))}
</div>
<div className='flex justify-center mt-2'>
<Drawer>

View File

@ -0,0 +1,388 @@
'use client';
import { useState, useEffect } from 'react';
import type React from 'react';
import { useAuth, useTVMode } from '@/components/context';
import type { UserWithStatus } from '@/lib/hooks';
import { BasedAvatar, Drawer, DrawerTrigger, Loading } from '@/components/ui';
import { StatusMessage, SubmitButton } from '@/components/default';
import { ConnectionStatus, HistoryDrawer } from '@/components/status';
import type { Profile } from '@/utils/supabase';
import { makeConditionalClassName } from '@/lib/utils';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { RefreshCw, Clock, Calendar, CheckCircle2 } from 'lucide-react';
import { useStatusData, useStatusSubscription } from '@/lib/hooks';
import { formatTime, formatDate } from '@/lib/utils';
import Link from 'next/link';
type ListProps = {
initialStatuses: UserWithStatus[];
};
export const StatusList = ({ initialStatuses = [] }: ListProps) => {
const { isAuthenticated } = useAuth();
const { tvMode } = useTVMode();
const [selectedUsers, setSelectedUsers] = useState<UserWithStatus[]>([]);
const [selectAll, setSelectAll] = useState(false);
const [statusInput, setStatusInput] = useState('');
const [selectedHistoryUser, setSelectedHistoryUser] =
useState<Profile | null>(null);
const [updateStatusMessage, setUpdateStatusMessage] = useState('');
const {
data: usersWithStatuses = initialStatuses,
isLoading: loading,
error,
refetch,
newStatuses,
updateStatusMutation,
} = useStatusData({
initialData: initialStatuses,
enabled: isAuthenticated,
});
const { connectionStatus, connect: reconnect } = useStatusSubscription(() => {
refetch().catch((error) => {
console.error('Error refetching statuses:', error);
});
});
const handleUpdateStatus = () => {
if (!isAuthenticated) {
setUpdateStatusMessage(
'Error: You must be signed in to update technician statuses!',
);
return;
}
if (statusInput.length < 3 || statusInput.length > 80) {
setUpdateStatusMessage(
'Error: Your status must be between 3 & 80 characters long!',
);
return;
}
updateStatusMutation.mutate({
usersWithStatuses: selectedUsers,
status: statusInput.trim(),
});
setSelectedUsers([]);
setStatusInput('');
setUpdateStatusMessage('');
};
const handleCardSelect = (user: UserWithStatus, e: React.MouseEvent) => {
// Prevent selection if clicking on profile elements
if ((e.target as HTMLElement).closest('[data-profile-trigger]')) {
return;
}
setSelectedUsers((prev) =>
prev.some((u) => u.user.id === user.user.id)
? prev.filter((prevUser) => prevUser.user.id !== user.user.id)
: [...prev, user],
);
};
const handleSelectAllChange = () => {
if (selectAll) {
setSelectedUsers([]);
} else {
setSelectedUsers(usersWithStatuses);
}
setSelectAll(!selectAll);
};
useEffect(() => {
setSelectAll(
selectedUsers.length === usersWithStatuses.length &&
usersWithStatuses.length > 0,
);
}, [selectedUsers.length, usersWithStatuses.length]);
if (loading) {
return (
<div className='flex justify-center items-center min-h-[400px]'>
<Loading className='w-full' alpha={0.5} />
</div>
);
}
if (error) {
return (
<div className='flex flex-col justify-center items-center min-h-[400px] gap-4'>
<p className='text-red-500'>Error loading status updates</p>
<Button onClick={() => refetch()} variant='outline'>
<RefreshCw className='w-4 h-4 mr-2' />
Retry
</Button>
</div>
);
}
const containerClassName = makeConditionalClassName({
context: tvMode,
defaultClassName: 'flex flex-col mx-auto space-y-3 items-center',
on: 'lg:w-11/12 w-full mt-8',
off: 'sm:w-5/6 md:w-3/4 lg:w-2/3 xl:w-1/2',
});
const headerClassName = makeConditionalClassName({
context: tvMode,
defaultClassName: 'w-full',
on: 'hidden',
off: 'flex mb-6 justify-between items-center',
});
const cardContainerClassName = makeConditionalClassName({
context: tvMode,
defaultClassName: 'w-full',
on: 'grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4',
off: 'space-y-2 w-full',
});
return (
<div className={containerClassName}>
<div className={headerClassName}>
<div className='flex items-center gap-6'>
<Button
variant='outline'
size='sm'
onClick={handleSelectAllChange}
className='flex items-center gap-2'
>
<CheckCircle2
className={`w-4 h-4 ${selectAll ? 'text-primary' : ''}`}
/>
{selectAll ? 'Deselect All' : 'Select All'}
</Button>
{!tvMode && (
<div className='flex items-center gap-2 text-sm text-muted-foreground'>
<span>Miss the old table?</span>
<Link
href='/status/table'
className='font-medium text-primary hover:underline'
>
Find it here!
</Link>
</div>
)}
</div>
<div className='flex items-center gap-2'>
<ConnectionStatus
status={connectionStatus}
onReconnect={reconnect}
showAsButton={connectionStatus === 'disconnected'}
/>
</div>
</div>
<div className={cardContainerClassName}>
{usersWithStatuses.map((userWithStatus) => {
const isSelected = selectedUsers.some(
(u) => u.user.id === userWithStatus.user.id,
);
const isNewStatus = newStatuses.has(userWithStatus);
const isUpdatedByOther =
userWithStatus.updated_by &&
userWithStatus.updated_by.id !== userWithStatus.user.id;
return (
<Card
key={userWithStatus.user.id}
className={`
relative transition-all duration-200 cursor-pointer hover:shadow-md
${tvMode ? 'p-4' : 'p-3'}
${isSelected ? 'ring-2 ring-primary bg-primary/5 shadow-md' : 'hover:bg-muted/30'}
${isNewStatus ? 'animate-in slide-in-from-top-2 duration-500 bg-green-50 border-green-200' : ''}
`}
onClick={(e) => handleCardSelect(userWithStatus, e)}
>
{isSelected && (
<div className='absolute top-2 right-2 text-primary'>
<CheckCircle2
className={`${tvMode ? 'w-6 h-6' : 'w-5 h-5'}`}
/>
</div>
)}
<CardContent className='p-0'>
<div className='flex items-start gap-3'>
{/* Profile Section - Clickable for history */}
<Drawer>
<DrawerTrigger asChild>
<div
data-profile-trigger
className='flex-shrink-0 cursor-pointer hover:opacity-80 transition-opacity'
onClick={() =>
setSelectedHistoryUser(userWithStatus.user)
}
>
<BasedAvatar
src={userWithStatus.user.avatar_url}
fullName={userWithStatus.user.full_name}
className={tvMode ? 'w-16 h-16' : 'w-12 h-12'}
/>
</div>
</DrawerTrigger>
{selectedHistoryUser === userWithStatus.user && (
<HistoryDrawer user={selectedHistoryUser} />
)}
</Drawer>
{/* Content Section */}
<div className='flex-1 min-w-0'>
{/* Header with name and timestamp */}
<div className='flex items-center justify-between mb-2'>
<Drawer>
<DrawerTrigger asChild>
<h3
data-profile-trigger
className={`
font-semibold cursor-pointer hover:underline truncate
${tvMode ? 'text-2xl' : 'text-base'}
`}
onClick={() =>
setSelectedHistoryUser(userWithStatus.user)
}
>
{userWithStatus.user.full_name}
</h3>
</DrawerTrigger>
{selectedHistoryUser === userWithStatus.user && (
<HistoryDrawer user={selectedHistoryUser} />
)}
</Drawer>
<div className='flex items-center gap-2 text-muted-foreground flex-shrink-0'>
<Clock
className={`${tvMode ? 'w-5 h-5' : 'w-4 h-4'}`}
/>
<span className={`${tvMode ? 'text-lg' : 'text-sm'}`}>
{formatTime(userWithStatus.created_at)}
</span>
</div>
</div>
{/* Status Content */}
<div
className={`
mb-2 leading-relaxed
${tvMode ? 'text-xl' : 'text-sm'}
`}
>
<p>{userWithStatus.status}</p>
</div>
{/* Footer with date and updated by info */}
<div className='flex items-center justify-between text-muted-foreground'>
<div className='flex items-center gap-2'>
<Calendar
className={`${tvMode ? 'w-4 h-4' : 'w-3 h-3'}`}
/>
<span className={`${tvMode ? 'text-base' : 'text-xs'}`}>
{formatDate(userWithStatus.created_at)}
</span>
</div>
{isUpdatedByOther && (
<div className='flex items-center gap-1'>
<BasedAvatar
src={userWithStatus.updated_by?.avatar_url}
fullName={userWithStatus.updated_by?.full_name}
className={`${tvMode ? 'w-5 h-5' : 'w-4 h-4'}`}
/>
<span
className={`${tvMode ? 'text-base' : 'text-xs'}`}
>
Updated by {userWithStatus.updated_by?.full_name}
</span>
</div>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
{usersWithStatuses.length === 0 && (
<Card className='p-8 text-center'>
<p
className={`text-muted-foreground ${tvMode ? 'text-2xl' : 'text-base'}`}
>
No status updates have been made in the past day.
</p>
</Card>
)}
{!tvMode && (
<Card className='p-6 mt-6 w-full'>
<div className='flex flex-col gap-4'>
<h3 className='text-lg font-semibold'>Update Status</h3>
<div className='flex flex-col gap-4'>
<div className='flex gap-4'>
<Input
autoFocus
type='text'
placeholder='Enter status update...'
className='flex-1 text-base'
value={statusInput}
disabled={updateStatusMutation.isPending}
onChange={(e) => setStatusInput(e.target.value)}
onKeyDown={(e) => {
if (
e.key === 'Enter' &&
!e.shiftKey &&
!updateStatusMutation.isPending
) {
e.preventDefault();
handleUpdateStatus();
}
}}
/>
<SubmitButton
onClick={handleUpdateStatus}
disabled={updateStatusMutation.isPending}
className='px-6'
>
{selectedUsers.length > 0
? `Update ${selectedUsers.length} ${selectedUsers.length > 1 ? 'users' : 'user'}`
: 'Update Status'}
</SubmitButton>
</div>
{updateStatusMessage &&
(updateStatusMessage.includes('Error') ||
updateStatusMessage.includes('error') ||
updateStatusMessage.includes('failed') ||
updateStatusMessage.includes('invalid') ? (
<StatusMessage message={{ error: updateStatusMessage }} />
) : (
<StatusMessage message={{ message: updateStatusMessage }} />
))}
</div>
<div className='flex justify-center mt-2'>
<Drawer>
<DrawerTrigger asChild>
<Button
variant='outline'
className={tvMode ? 'text-xl p-6' : ''}
>
View All Status History
</Button>
</DrawerTrigger>
<HistoryDrawer />
</Drawer>
</div>
</div>
</Card>
)}
</div>
);
};

View File

@ -10,11 +10,10 @@ import { makeConditionalClassName } from '@/lib/utils';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { RefreshCw, Clock, Calendar } from 'lucide-react';
import { useSharedStatusSubscription, useStatusData } from '@/lib/hooks';
import { useStatusSubscription, useStatusData } from '@/lib/hooks';
import { formatTime, formatDate } from '@/lib/utils';
import Link from 'next/link';
type TableProps = {
initialStatuses: UserWithStatus[];
};
@ -42,31 +41,31 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
enabled: isAuthenticated,
});
// In your StatusList component
const { connectionStatus, connect: reconnect } = useSharedStatusSubscription(() => {
const { connectionStatus, connect: reconnect } = useStatusSubscription(() => {
refetch().catch((error) => {
console.error('Error refetching statuses:', error);
});
});
//const { connectionStatus, connect: reconnect } = useStatusSubscription({
//enabled: isAuthenticated,
//onStatusUpdate: () => {
//refetch().catch((error) => {
//console.error('Error refetching statuses:', error);
//});
//},
//enabled: isAuthenticated,
//onStatusUpdate: () => {
//refetch().catch((error) => {
//console.error('Error refetching statuses:', error);
//});
//},
//});
const handleUpdateStatus = () => {
if (!isAuthenticated) {
setUpdateStatusMessage(
'Error: You must be signed in to update technician statuses!'
'Error: You must be signed in to update technician statuses!',
);
return;
}
if (statusInput.length < 3 || statusInput.length > 80) {
setUpdateStatusMessage(
'Error: Your status must be between 3 & 80 characters long!'
'Error: Your status must be between 3 & 80 characters long!',
);
return;
}
@ -85,7 +84,7 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
setSelectedUsers((prev) =>
prev.some((u) => u.user.id === user.user.id)
? prev.filter((prevUser) => prevUser.user.id !== user.user.id)
: [...prev, user]
: [...prev, user],
);
};
@ -101,7 +100,7 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
useEffect(() => {
setSelectAll(
selectedUsers.length === usersWithStatuses.length &&
usersWithStatuses.length > 0
usersWithStatuses.length > 0,
);
}, [selectedUsers.length, usersWithStatuses.length]);
@ -163,7 +162,7 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
/>
{!tvMode && (
<div className='flex flex-row gap-2'>
<p>Tired of the old table? {' '}</p>
<p>Tired of the old table? </p>
<Link
href='/status/list'
className='italic font-semibold text-accent-foreground'
@ -249,9 +248,7 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
fullName={userWithStatus.updated_by?.full_name}
className='w-5 h-5'
/>
<span
className={tvMode ? 'text-lg' : 'text-base'}
>
<span className={tvMode ? 'text-lg' : 'text-base'}>
Updated by {userWithStatus.updated_by.full_name}
</span>
</div>
@ -279,7 +276,9 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
<div className='flex items-start xl:w-1/6'></div>
<div className='flex flex-col my-auto items-start'>
<div className='flex gap-4 my-1'>
<Clock className={`${tvMode ? 'lg:w-11 lg:h-11' : 'lg:w-9 lg:h-9'}`} />
<Clock
className={`${tvMode ? 'lg:w-11 lg:h-11' : 'lg:w-9 lg:h-9'}`}
/>
{formatTime(userWithStatus.created_at)}
</div>
<div className='flex gap-4 my-1'>
@ -315,7 +314,7 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
<StatusMessage message={{ error: updateStatusMessage }} />
) : (
<StatusMessage message={{ message: updateStatusMessage }} />
))}
))}
{!tvMode && (
<div className='mx-auto flex flex-row items-center justify-center py-5 gap-4'>
@ -352,8 +351,7 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
{selectedUsers.length > 0
? `Update status for ${selectedUsers.length}
${selectedUsers.length > 1 ? 'users' : 'user'}`
: 'Update status'
}
: 'Update status'}
</SubmitButton>
</div>
)}
@ -363,7 +361,10 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
<div className='flex justify-center mt-6'>
<Drawer>
<DrawerTrigger asChild>
<Button variant='outline' className={tvMode ? 'text-3xl p-6' : ''}>
<Button
variant='outline'
className={tvMode ? 'text-3xl p-6' : ''}
>
View All Status History
</Button>
</DrawerTrigger>
@ -374,4 +375,3 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
</div>
);
};

View File

@ -1,4 +1,5 @@
export * from './ConnectionStatus';
export * from './HistoryDrawer';
export * from './List';
//export * from './List';
export * from './StatusList';
export * from './Table';