Final cleanup. Prepping for deployment.
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
'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';
|
||||
@ -7,11 +8,10 @@ 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, CardHeader } from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RefreshCw, Clock, Calendar } from 'lucide-react';
|
||||
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';
|
||||
@ -43,7 +43,6 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
// In your StatusList component
|
||||
const { connectionStatus, connect: reconnect } = useStatusSubscription(() => {
|
||||
refetch().catch((error) => {
|
||||
console.error('Error refetching statuses:', error);
|
||||
@ -75,7 +74,12 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
setUpdateStatusMessage('');
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (user: UserWithStatus) => {
|
||||
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)
|
||||
@ -121,54 +125,46 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
|
||||
const containerClassName = makeConditionalClassName({
|
||||
context: tvMode,
|
||||
defaultClassName: 'flex flex-col mx-auto space-y-4 items-center',
|
||||
on: 'lg:w-11/12 w-full mt-15',
|
||||
off: 'sm:w-5/6 md:3/4 lg:w-1/2',
|
||||
defaultClassName:
|
||||
'flex flex-col mx-auto items-center\
|
||||
sm:w-5/6 md:w-3/4 lg:w-2/3 xl:w-1/2 min-w-[450px]',
|
||||
on: 'mt-8',
|
||||
off: 'px-10',
|
||||
});
|
||||
|
||||
const headerClassName = makeConditionalClassName({
|
||||
context: tvMode,
|
||||
defaultClassName: 'w-full',
|
||||
on: 'hidden',
|
||||
off: 'flex mb-4 justify-between',
|
||||
off: 'flex mb-3 justify-between items-center',
|
||||
});
|
||||
|
||||
const cardContainerClassName = makeConditionalClassName({
|
||||
context: tvMode,
|
||||
defaultClassName: '',
|
||||
on: '',
|
||||
off: 'space-y-3 items-center justify-center w-full',
|
||||
});
|
||||
|
||||
const cardClassName = makeConditionalClassName({
|
||||
context: tvMode,
|
||||
defaultClassName:
|
||||
'transition-all duration-300 hover:shadow-md hover:bg-muted/50 cursor-pointer',
|
||||
on: 'lg:text-4xl',
|
||||
off: 'lg:text-base lg:w-full',
|
||||
defaultClassName: 'w-full space-y-2',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<div className={headerClassName}>
|
||||
<div className='flex items-center gap-10'>
|
||||
<div className='flex gap-2'>
|
||||
<Checkbox
|
||||
id='select-all'
|
||||
checked={selectAll}
|
||||
onCheckedChange={handleSelectAllChange}
|
||||
className='size-6'
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={handleSelectAllChange}
|
||||
className='flex items-center gap-2'
|
||||
>
|
||||
<CheckCircle2
|
||||
className={`w-4 h-4 ${selectAll ? 'text-primary' : ''}`}
|
||||
/>
|
||||
<label htmlFor='select-all' className='font-medium'>
|
||||
Select All
|
||||
</label>
|
||||
</div>
|
||||
{selectAll ? 'Deselect All' : 'Select All'}
|
||||
</Button>
|
||||
{!tvMode && (
|
||||
<div className='flex flex-row gap-2'>
|
||||
<p>Miss the old table?</p>
|
||||
<div className='flex items-center gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>Miss the old table?</span>
|
||||
<Link
|
||||
href='/status/table'
|
||||
className='italic font-semibold text-accent-foreground'
|
||||
className='font-medium hover:underline'
|
||||
>
|
||||
Find it here!
|
||||
</Link>
|
||||
@ -198,88 +194,119 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
<Card
|
||||
key={userWithStatus.user.id}
|
||||
className={`
|
||||
${cardClassName}
|
||||
${isSelected ? 'ring-2 ring-primary' : ''}
|
||||
${isNewStatus ? 'animate-pulse bg-primary/5 border-primary/20' : ''}
|
||||
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)}
|
||||
>
|
||||
<CardHeader className='pb-3'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-3'>
|
||||
{!tvMode && (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(userWithStatus)
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
{isSelected && (
|
||||
<div className='absolute top-2 right-2 text-primary'>
|
||||
<CheckCircle2
|
||||
className={`${tvMode ? 'w-6 h-6' : 'w-5 h-5'}`}
|
||||
/>
|
||||
)}
|
||||
<BasedAvatar
|
||||
src={userWithStatus.user.avatar_url}
|
||||
fullName={userWithStatus.user.full_name}
|
||||
className={tvMode ? 'w-24 h-24' : 'w-16 h-16'}
|
||||
/>
|
||||
<div className='my-auto'>
|
||||
<h3
|
||||
className={`font-semibold ${tvMode ? 'text-5xl' : 'text-2xl'}`}
|
||||
>
|
||||
{userWithStatus.user.full_name}
|
||||
</h3>
|
||||
{isUpdatedByOther && (
|
||||
<div className='flex items-center gap-1 text-muted-foreground'>
|
||||
<BasedAvatar
|
||||
src={userWithStatus.updated_by?.avatar_url}
|
||||
fullName={userWithStatus.updated_by?.full_name}
|
||||
className='w-5 h-5'
|
||||
/>
|
||||
<span
|
||||
className={`${tvMode ? 'text-3xl' : 'text-sm'}`}
|
||||
>
|
||||
Updated by {userWithStatus.updated_by?.full_name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='my-auto'>
|
||||
<div className='flex items-center gap-2 text-muted-foreground'>
|
||||
<Clock className={`${tvMode ? 'w-8 h-8' : 'w-6 h-6'}`} />
|
||||
<span className={`${tvMode ? 'text-3xl' : 'text-xl'}`}>
|
||||
{formatTime(userWithStatus.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 text-muted-foreground'>
|
||||
<Calendar
|
||||
className={`${tvMode ? 'w-8 h-8' : 'w-6 h-6'}`}
|
||||
/>
|
||||
<span className={`${tvMode ? 'text-3xl' : 'text-xl'}`}>
|
||||
{formatDate(userWithStatus.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className='pt-0'>
|
||||
|
||||
<CardContent className='p-0'>
|
||||
<div className='flex items-start gap-3'>
|
||||
{/* Profile Section - Clickable for history */}
|
||||
<Drawer>
|
||||
<DrawerTrigger asChild>
|
||||
<div
|
||||
className={`
|
||||
p-4 rounded-lg bg-muted/30 hover:bg-muted/50
|
||||
transition-colors cursor-pointer text-left
|
||||
${tvMode ? 'text-4xl' : 'text-xl'}
|
||||
`}
|
||||
data-profile-trigger
|
||||
className='flex-shrink-0 cursor-pointer hover:opacity-80 transition-opacity'
|
||||
onClick={() =>
|
||||
setSelectedHistoryUser(userWithStatus.user)
|
||||
}
|
||||
>
|
||||
<p className='font-medium'>{userWithStatus.status}</p>
|
||||
<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'>
|
||||
{/* Header with name and timestamp */}
|
||||
<div className='flex items-start justify-between mb-2'>
|
||||
<div>
|
||||
<Drawer>
|
||||
<DrawerTrigger asChild>
|
||||
<h3
|
||||
data-profile-trigger
|
||||
className={`
|
||||
font-semibold cursor-pointer hover:text-primary/80 truncate
|
||||
${tvMode ? 'text-3xl' : 'text-2xl'}
|
||||
`}
|
||||
onClick={() =>
|
||||
setSelectedHistoryUser(userWithStatus.user)
|
||||
}
|
||||
>
|
||||
{userWithStatus.user.full_name}
|
||||
</h3>
|
||||
</DrawerTrigger>
|
||||
{selectedHistoryUser === userWithStatus.user && (
|
||||
<HistoryDrawer user={selectedHistoryUser} />
|
||||
)}
|
||||
</Drawer>
|
||||
<div
|
||||
className={`pl-2 pr-15 pt-2 ${tvMode ? 'text-2xl' : 'text-xl'}`}
|
||||
>
|
||||
<p>{userWithStatus.status}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col items-end px-2 gap-2 text-muted-foreground flex-shrink-0'>
|
||||
<div className='flex items-center gap-2 flex-shrink-0 w-full'>
|
||||
<Clock
|
||||
className={`${tvMode ? 'w-6 h-6' : 'w-5 h-5'}`}
|
||||
/>
|
||||
<span
|
||||
className={`${tvMode ? 'text-2xl' : 'text-xl'}`}
|
||||
>
|
||||
{formatTime(userWithStatus.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 flex-shrink-0 w-full'>
|
||||
<Calendar
|
||||
className={`${tvMode ? 'w-6 h-6' : 'w-5 h-5'}`}
|
||||
/>
|
||||
<span
|
||||
className={`${tvMode ? 'text-2xl' : 'text-xl'}`}
|
||||
>
|
||||
{formatDate(userWithStatus.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 flex-shrink-0'>
|
||||
{isUpdatedByOther && (
|
||||
<div className='flex items-center gap-2'>
|
||||
<BasedAvatar
|
||||
src={userWithStatus.updated_by?.avatar_url}
|
||||
fullName={userWithStatus.updated_by?.full_name}
|
||||
className={`${tvMode ? 'w-6 h-6' : 'w-5 h-5'}`}
|
||||
/>
|
||||
<span
|
||||
className={`${tvMode ? 'text-base' : 'text-sm'}`}
|
||||
>
|
||||
<div className='flex flex-col'>
|
||||
<p>Updated by</p>
|
||||
{userWithStatus.updated_by?.full_name}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@ -289,7 +316,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
{usersWithStatuses.length === 0 && (
|
||||
<Card className='p-8 text-center'>
|
||||
<p
|
||||
className={`text-muted-foreground ${tvMode ? 'text-4xl' : 'text-lg'}`}
|
||||
className={`text-muted-foreground ${tvMode ? 'text-2xl' : 'text-lg'}`}
|
||||
>
|
||||
No status updates have been made in the past day.
|
||||
</p>
|
||||
@ -305,8 +332,8 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
<Input
|
||||
autoFocus
|
||||
type='text'
|
||||
placeholder='Enter status'
|
||||
className='flex-1 text-base'
|
||||
placeholder='Enter status update...'
|
||||
className='flex-1 text-2xl'
|
||||
value={statusInput}
|
||||
disabled={updateStatusMutation.isPending}
|
||||
onChange={(e) => setStatusInput(e.target.value)}
|
||||
@ -327,9 +354,8 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
className='px-6'
|
||||
>
|
||||
{selectedUsers.length > 0
|
||||
? `Update status for ${selectedUsers.length}
|
||||
${selectedUsers.length > 1 ? 'users' : 'user'}`
|
||||
: 'Update status'}
|
||||
? `Update ${selectedUsers.length} ${selectedUsers.length > 1 ? 'users' : 'user'}`
|
||||
: 'Update Status'}
|
||||
</SubmitButton>
|
||||
</div>
|
||||
{updateStatusMessage &&
|
||||
@ -347,7 +373,7 @@ export const StatusList = ({ initialStatuses = [] }: ListProps) => {
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
className={tvMode ? 'text-3xl p-6' : ''}
|
||||
className={tvMode ? 'text-xl p-6' : ''}
|
||||
>
|
||||
View All Status History
|
||||
</Button>
|
||||
|
@ -1,388 +0,0 @@
|
||||
'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>
|
||||
);
|
||||
};
|
@ -139,14 +139,14 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
|
||||
const thClassName = makeConditionalClassName({
|
||||
context: tvMode,
|
||||
defaultClassName: 'py-4 px-4 border font-semibold ',
|
||||
on: 'lg:text-6xl xl:min-w-[420px]',
|
||||
off: 'lg:text-5xl xl:min-w-[300px]',
|
||||
on: 'lg:text-5xl xl:min-w-[420px]',
|
||||
off: 'lg:text-4xl xl:min-w-[320px]',
|
||||
});
|
||||
const tdClassName = makeConditionalClassName({
|
||||
context: tvMode,
|
||||
defaultClassName: 'py-2 px-2 border',
|
||||
on: 'lg:text-5xl',
|
||||
off: 'lg:text-4xl',
|
||||
on: 'lg:text-4xl',
|
||||
off: 'lg:text-3xl',
|
||||
});
|
||||
const tCheckboxClassName = `py-3 px-4 border`;
|
||||
const checkBoxClassName = `lg:scale-200 cursor-pointer`;
|
||||
@ -161,13 +161,13 @@ export const TechTable = ({ initialStatuses = [] }: TableProps) => {
|
||||
showAsButton={connectionStatus === 'disconnected'}
|
||||
/>
|
||||
{!tvMode && (
|
||||
<div className='flex flex-row gap-2'>
|
||||
<p>Tired of the old table? </p>
|
||||
<div className='flex flex-row gap-2 text-xs'>
|
||||
<p className='text-muted-foreground'>Tired of the old table? </p>
|
||||
<Link
|
||||
href='/status/list'
|
||||
className='italic font-semibold text-accent-foreground'
|
||||
className='italic font-semibold hover:text-primary/80'
|
||||
>
|
||||
Try out the new status list!
|
||||
Try the new status list!
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
@ -248,7 +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-xl' : 'text-base'}>
|
||||
Updated by {userWithStatus.updated_by.full_name}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from './ConnectionStatus';
|
||||
export * from './HistoryDrawer';
|
||||
//export * from './List';
|
||||
export * from './StatusList';
|
||||
export * from './List';
|
||||
export * from './Table';
|
||||
|
Reference in New Issue
Block a user